📜 ⬆️ ⬇️

Library Header2ActionBar for Android

Library Header2ActionBar for Android



(demo to attract attention)


You, probably, have already seen similar in applications from Google (Play Music, Google Press) and, perhaps, any others. For these purposes, there is already a long time ago a library from ManuelPeinado - FadingActionBar , which perfectly performs its task, but unfortunately, has two “fatal” disadvantages.

The second one is described as a known issue:
Known issues
')
There is an important issue with the library and listviews. It’s re-created due to a configuration change. So, I strongly suggest that I’m looking for it.


Trying to correct this shortcoming, I decided to write my own implementation, thus eliminating both shortcomings :)



The library consists of three files:
FadingActionBarActivity.java
/** * Created by AChep@xda <artemchep@gmail.com> */ public class FadingActionBarActivity extends Activity { private static final String TAG = "FadingActionBarActivity"; private int mAlpha = 255; private Drawable mDrawable; private boolean isAlphaLocked; public void setActionBarBackgroundDrawable(Drawable drawable) { getActionBar().setBackgroundDrawable(drawable); mDrawable = drawable; if (mAlpha == 255) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) mAlpha = drawable.getAlpha(); } else { setActionBarAlpha(mAlpha); } } /** * An {@link android.app.ActionBar} background drawable. * * @see #setActionBarBackgroundDrawable(android.graphics.drawable.Drawable) * @see #setActionBarAlpha(int) */ public Drawable getActionBarBackgroundDrawable() { return mDrawable; } /** * Please use this method for global changes only! * Otherwise, please, use {@link android.graphics.drawable.Drawable#setAlpha(int)} * to {@link #getActionBarBackgroundDrawable()} directly. * * @param alpha a value from 0 to 255 * @see #getActionBarBackgroundDrawable() * @see #getActionBarAlpha() */ public void setActionBarAlpha(int alpha) { if (mDrawable == null) { Log.w(TAG, "Set action bar background before setting alpha!"); return; } if (!isAlphaLocked) mDrawable.setAlpha(alpha); mAlpha = alpha; } public int getActionBarAlpha() { return mAlpha; } public void setActionBarAlphaLocked(boolean isLocked) { isAlphaLocked = isLocked; } } 
HeaderFragment .java
 /** * Little header fragment. * <p> * Created by AChep@xda <artemchep@gmail.com> * </p> */ public class HeaderFragment extends Fragment { private static final String TAG = "HeaderFragment"; private FrameLayout mRoot; private View mContentOverlay; private View mHeader; private int mHeaderHeight; private int mCurrentHeaderHeight; private int mCurrentHeaderTranslateY; private Space mFakeHeader; private boolean mListViewEmpty; private OnHeaderScrollChangeListener mOnHeaderScrollChangeListener; public interface OnHeaderScrollChangeListener { public void onHeaderScrollChanged(float progress, int height, int scroll); } public void setOnHeaderScrollChangeListener(OnHeaderScrollChangeListener listener) { mOnHeaderScrollChangeListener = listener; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final Activity activity = getActivity(); mHeader = inflater.inflate(getHeaderResource(), container, false); mHeaderHeight = mHeader.getLayoutParams().height; mCurrentHeaderHeight = mHeaderHeight; mCurrentHeaderTranslateY = 0; onPrepareHeaderView(mHeader); // Perform fake header view. mFakeHeader = new Space(activity); mFakeHeader.setLayoutParams(new ListView.LayoutParams( 0, mHeaderHeight)); View content = inflater.inflate(getContentResource(), container, false); assert content != null; if (content instanceof ListView) { final ListView listView = (ListView) content; mListViewEmpty = true; listView.addHeaderView(mFakeHeader); onPrepareContentListView(listView); listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView absListView, int i) { /* unused */ } @Override public void onScroll(AbsListView absListView, int i, int i2, int i3) { if (mListViewEmpty) { // poor poor listview :( updateHeaderScroll(0); } else { final View child = absListView.getChildAt(0); if (child == mFakeHeader) { updateHeaderScroll(child.getTop()); } else { updateHeaderScroll(-mHeaderHeight); } } } }); } else { onPrepareContentView(content); // Merge fake header view and content view final LinearLayout ll = new LinearLayout(activity); ll.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); ll.setOrientation(LinearLayout.VERTICAL); ll.addView(mFakeHeader); ll.addView(content); final NotifyingScrollView scrollView = new NotifyingScrollView(activity); scrollView.addView(ll); scrollView.setOnScrollChangedListener(new NotifyingScrollView.OnScrollChangedListener() { @Override public void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt) { updateHeaderScroll(-t); } }); content = scrollView; } mRoot = new FrameLayout(activity); mRoot.addView(content, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mRoot.addView(mHeader); // Overlay view always shows at the top of content. mContentOverlay = onCreateContentOverlayView(); if (mContentOverlay != null) { mRoot.addView(mContentOverlay, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } // Initial notify notifyOnHeaderScrollChangeListener(0, mHeaderHeight, 0); return mRoot; } private void updateHeaderScroll(int scrollTo) { scrollTo = scrollTo > 0 ? 0 : scrollTo < -mHeaderHeight ? -mHeaderHeight : scrollTo; final boolean allowChangeHeight = isHeaderHeightFloating(); final int height = mHeaderHeight + scrollTo / 2; final int transY = allowChangeHeight ? scrollTo / 2 : scrollTo; if (height != mCurrentHeaderHeight && allowChangeHeight) { final ViewGroup.LayoutParams lp = mHeader.getLayoutParams(); lp.height = height; mHeader.setLayoutParams(lp); mCurrentHeaderHeight = height; } if (transY != mCurrentHeaderTranslateY) { mHeader.setTranslationY(transY); mCurrentHeaderTranslateY = transY; if (mContentOverlay != null) { final ViewGroup.LayoutParams lp = mContentOverlay.getLayoutParams(); final int delta = mHeaderHeight + scrollTo; lp.height = mRoot.getHeight() - delta; mContentOverlay.setLayoutParams(lp); mContentOverlay.setTranslationY(delta); } notifyOnHeaderScrollChangeListener((float) -scrollTo / mHeaderHeight, mHeaderHeight, -scrollTo); } } private void notifyOnHeaderScrollChangeListener(float progress, int height, int scroll) { if (mOnHeaderScrollChangeListener != null) { // Notify upper fragment to update ActionBar's alpha or whatever. mOnHeaderScrollChangeListener.onHeaderScrollChanged(progress, height, scroll); } } /** * If true, header's height might be changed on scroll. * <p>Note: It takes a lot of calculations to measure the header all the time.</p> */ public boolean isHeaderHeightFloating() { return false; } /** * Int reference to header's resource. * * @see #onPrepareHeaderView(android.view.View) * @see #getContentResource() */ public int getHeaderResource() { return 0; } /** * This is the place for setting up the header. * * @param view inflated header view. * @see #getHeaderResource() */ public void onPrepareHeaderView(View view) { /* for my child */ } /** * Int reference to content's resource. * <p> * <b>Attention</b>: Parent view must be {@link android.widget.ListView ListView} * or something else which will work inside of {@link android.widget.ScrollView ScrollView}. * Otherwise it <b>WON'T</b> work. * </p> * * @see #getHeaderResource() * @see #onPrepareContentListView(ListView) */ public int getContentResource() { return 0; } /** * Called if the content's parent is a {@link android.widget.ListView ListView}. * * @see #getContentResource() * @see #setListViewAdapter(android.widget.ListView, android.widget.ListAdapter) */ public void onPrepareContentListView(ListView listView) { /* for my child */ } public void setListViewAdapter(ListView listView, ListAdapter adapter) { mListViewEmpty = adapter == null; listView.removeHeaderView(mFakeHeader); listView.addHeaderView(mFakeHeader); listView.setAdapter(adapter); } /** * Called if the content's parent is NOT a {@link android.widget.ListView ListView}. * * @see #getContentResource() */ public void onPrepareContentView(View view) { /* for my child */ } public View onCreateContentOverlayView() { return null; } } 
NotifyingScrollView .java
 /** * @author Cyril Mottier with modifications from Manuel Peinado */ public class NotifyingScrollView extends ScrollView { // Edge-effects don't mix well with the translucent action bar in Android 2.X private boolean mDisableEdgeEffects = true; /** * @author Cyril Mottier */ public interface OnScrollChangedListener { void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt); } private OnScrollChangedListener mOnScrollChangedListener; public NotifyingScrollView(Context context) { super(context); } public NotifyingScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public NotifyingScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (mOnScrollChangedListener != null) { mOnScrollChangedListener.onScrollChanged(this, l, t, oldl, oldt); } } public void setOnScrollChangedListener(OnScrollChangedListener listener) { mOnScrollChangedListener = listener; } @Override protected float getTopFadingEdgeStrength() { // http://stackoverflow.com/a/6894270/244576 if (mDisableEdgeEffects && Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { return 0.0f; } return super.getTopFadingEdgeStrength(); } @Override protected float getBottomFadingEdgeStrength() { // http://stackoverflow.com/a/6894270/244576 if (mDisableEdgeEffects && Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { return 0.0f; } return super.getBottomFadingEdgeStrength(); } } 


and lies on GitHub 'e as a library project created in Android Studio.

Using


HeaderFragment and FadingActionBarActivity are inherited from the native counterparts, so while Android <4.0 is not supported out of the box.


Our application will be like a demo in the screenshot above. So, an example of an Activity:
 public class MainActivity extends FadingActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //   ActionBar'a setActionBarBackgroundDrawable(getResources().getDrawable(R.drawable.actionbar_bg)); FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.container, new TestHeaderFragment() ).commit(); } } 

 public class TestHeaderFragment extends HeaderFragment { @Override public void onAttach(Activity activity) { super.onAttach(activity); //   ActionBar'a    setOnHeaderScrollChangeListener(new OnHeaderScrollChangeListener() { @Override public void onHeaderScrollChanged(float progress, int height, int scroll) { height -= getActivity().getActionBar().getHeight(); progress = (float) scroll / height; if (progress > 1f) progress = 1f; ((FadingActionBarActivity) getActivity()).setActionBarAlpha((int) (255 * progress)); } }); } @Override public int getHeaderResource() { return R.layout.header; } @Override public void onPrepareHeaderView(View view) { super.onPrepareHeaderView(view); //  view  } @Override public int getContentResource() { return R.layout.content; } @Override public void onPrepareContentListView(ListView listView) { super.onPrepareContentListView(listView); //  view  setListViewAdapter(listView, new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, android.R.id.title, new String[]{"Android", "Android", "Android", "Android", "Android", "Android", "Android", "Android", "Android", "Android", "Android"})); } 


You also need to add a flag to the Activity style:
 <item name="android:windowActionBarOverlay">true</item> 

to allow content to be under ActionBar


I cover my face with my hands in advance and I apologize for my code and English. :(

Source: https://habr.com/ru/post/204940/


All Articles