Библиотека Header2ActionBar для Android

в 11:08, , рубрики: achep, android, fun, github, library, opensource, Разработка под android, метки: , , , , ,

Библиотека Header2ActionBar для Android

Библиотека Header2ActionBar для Android

(демо для привлечения внимания)

Вы, наверное, уже видели похожее в приложениях от Google (Play Музыка, Google Пресса) и, возможно, каких-либо других. Для этих целей уже довольно давно существует библиотека от ManuelPeinadoFadingActionBar, которая прекрасно выполняет свою задачу, но к сожалению, имеет два «фатальных» недостатка.

Второй из них описан как известная проблема:

Known Issues

There is an important issue with the library and ListViews. More specifically, things don't work quite right when the activity is re-created due to a configuration change. So, unless you handle configuration changes yourself (or your activity is portrait/landscape only), I strongly suggest you stick to having your content in a ScrollView until a solution to this issue is found.

Стараясь исправить этот недостаток, я решил написать свою реализацию, тем самым устранив и оба недостатка :)



Библиотека состоит из трёх файлов:

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 View mHeader;
    private int mHeaderHeight;
    private int mCurrentHeaderHeight;
    private int mCurrentHeaderTranslateY;

    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);

        View content = inflater.inflate(getContentResource(), container, false);
        assert content != null;
        if (content instanceof ListView) {
            final ListView listView = (ListView) content;

            // Perform fake header view.
            final Space listFakeHeader = new Space(activity);
            listFakeHeader.setLayoutParams(new ListView.LayoutParams(
                    0, mHeaderHeight));

            onPrepareContentListView(listView);
            listView.addHeaderView(listFakeHeader);
            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) {
                    final View child = absListView.getChildAt(0);
                    if (child == listFakeHeader) {
                        updateHeaderScroll(child.getTop());
                    } else {
                        updateHeaderScroll(-mHeaderHeight);
                    }
                }
            });
        } else {
            onPrepareContentView(content);

            final NotifyingScrollView scrollView = new NotifyingScrollView(activity);
            scrollView.addView(content);
            scrollView.setOnScrollChangedListener(new NotifyingScrollView.OnScrollChangedListener() {
                @Override
                public void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt) {
                    updateHeaderScroll(-t);
                }
            });
            content = scrollView;
        }

        final FrameLayout root = new FrameLayout(activity);
        root.addView(content, new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        root.addView(mHeader);
        return root;
    }

    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 (mOnHeaderScrollChangeListener != null) {
                // Notify upper fragment to update ActionBar's alpha or whatever.
                int scroll = Math.abs(scrollTo);
                mOnHeaderScrollChangeListener.onHeaderScrollChanged(
                        (float) scroll / mHeaderHeight, mHeaderHeight, 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()
     */
    public void onPrepareContentListView(ListView listView) { /* for my child */ }

    /**
     * Called if the content's parent is NOT a {@link android.widget.ListView ListView}.
     *
     * @see #getContentResource()
     */
    public void onPrepareContentView(View view) { /* for my child */ }

}
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();
    }
}

и лежит на GitHub'е как проект библиотеки созданной в Android Studio.

Использование

HeaderFragment и FadingActionBarActivity наследуются от нативных собратьев, так что пока Android < 4.0 не поддерживается из коробки.

Наше приложение будет подобием демо на скриншоте сверху. Итак, пример 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 контентом
        listView.setAdapter(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"}));
    }

Я заранее закрываю свое лицо руками и прошу прощения за свой код и английский. :(

Автор: AChep

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js