com.zzc.androidtrain.view.refresh.SmoothAppBarLayout.java Source code

Java tutorial

Introduction

Here is the source code for com.zzc.androidtrain.view.refresh.SmoothAppBarLayout.java

Source

/*
 * Copyright 2016 "Henry Tao <hi@henrytao.me>"
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.zzc.androidtrain.view.refresh;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;

import com.zzc.androidtrain.R;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

/**
 * Created by henrytao on 2/1/16.
 */
@CoordinatorLayout.DefaultBehavior(SmoothAppBarLayout.Behavior.class)
public class SmoothAppBarLayout extends AppBarLayout {

    private static final String ARG_CURRENT_OFFSET = "ARG_CURRENT_OFFSET";

    private static final String ARG_SUPER = "ARG_SUPER";

    public static boolean DEBUG = false;

    protected final List<WeakReference<OnOffsetChangedListener>> mOffsetChangedListeners = new ArrayList<>();

    protected boolean mHaveChildWithInterpolator;

    private int mRestoreCurrentOffset;

    private ScrollTargetCallback mScrollTargetCallback;

    private com.zzc.androidtrain.view.refresh.OnOffsetChangedListener mSyncOffsetListener;

    private int mTargetId;

    private int mViewPagerId;

    private ViewPager vViewPager;

    public SmoothAppBarLayout(Context context) {
        super(context);
        init(context, null);
    }

    public SmoothAppBarLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    @Override
    public void addOnOffsetChangedListener(OnOffsetChangedListener listener) {
        super.addOnOffsetChangedListener(listener);
        int i = 0;
        for (int z = this.mOffsetChangedListeners.size(); i < z; ++i) {
            WeakReference ref = (WeakReference) this.mOffsetChangedListeners.get(i);
            if (ref != null && ref.get() == listener) {
                return;
            }
        }
        this.mOffsetChangedListeners.add(new WeakReference(listener));
    }

    @Override
    public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) {
        super.removeOnOffsetChangedListener(listener);
        Iterator i = mOffsetChangedListeners.iterator();
        while (true) {
            OnOffsetChangedListener item;
            do {
                if (!i.hasNext()) {
                    return;
                }
                WeakReference ref = (WeakReference) i.next();
                item = (OnOffsetChangedListener) ref.get();
            } while (item != listener && item != null);
            i.remove();
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        initViews();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        int i = 0;
        for (int z = this.getChildCount(); i < z; ++i) {
            View child = this.getChildAt(i);
            LayoutParams childLp = (LayoutParams) child.getLayoutParams();
            Interpolator interpolator = childLp.getScrollInterpolator();
            if (interpolator != null) {
                mHaveChildWithInterpolator = true;
                break;
            }
        }
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        Bundle bundle = (Bundle) state;
        mRestoreCurrentOffset = bundle.getInt(ARG_CURRENT_OFFSET);
        Parcelable superState = bundle.getParcelable(ARG_SUPER);
        super.onRestoreInstanceState(superState);
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putInt(ARG_CURRENT_OFFSET, getCurrentOffset());
        bundle.putParcelable(ARG_SUPER, super.onSaveInstanceState());
        return bundle;
    }

    public int getCurrentOffset() {
        return -Utils.parseInt(getTag(R.id.tag_current_offset));
    }

    public void setScrollTargetCallback(ScrollTargetCallback scrollTargetCallback) {
        mScrollTargetCallback = scrollTargetCallback;
    }

    public void syncOffset(int newOffset) {
        syncOffset(newOffset, false);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.SmoothAppBarLayout, 0, 0);
        try {
            mViewPagerId = a.getResourceId(R.styleable.SmoothAppBarLayout_sabl_view_pager_id, 0);
            mTargetId = a.getResourceId(R.styleable.SmoothAppBarLayout_sabl_target_id, 0);
        } finally {
            a.recycle();
        }
    }

    private void initViews() {
        if (mViewPagerId > 0) {
            vViewPager = (ViewPager) getRootView().findViewById(mViewPagerId);
        } else {
            int i = 0;
            ViewGroup parent = (ViewGroup) getParent();
            View child;
            for (int z = parent.getChildCount(); i < z; i++) {
                child = parent.getChildAt(i);
                if (child instanceof ViewPager) {
                    vViewPager = (ViewPager) child;
                    break;
                }
            }
        }
    }

    private void setSyncOffsetListener(
            com.zzc.androidtrain.view.refresh.OnOffsetChangedListener syncOffsetListener) {
        mSyncOffsetListener = syncOffsetListener;
        syncOffset(mRestoreCurrentOffset, true);
    }

    private void syncOffset(int newOffset, boolean force) {
        mRestoreCurrentOffset = newOffset;
        if (mSyncOffsetListener != null) {
            mSyncOffsetListener.onOffsetChanged(this, newOffset, force);
        }
    }

    public static class Behavior extends BaseBehavior {

        protected ScrollFlag mScrollFlag;

        private int mLastY;

        private int mStatusBarSize;

        private ViewPager vViewPager;

        @Override
        protected void onInit(CoordinatorLayout coordinatorLayout, final AppBarLayout child) {
            Utils.log("widget | onInit");
            if (mScrollFlag == null) {
                mScrollFlag = new ScrollFlag(child);
            }
            if (child instanceof SmoothAppBarLayout) {
                final SmoothAppBarLayout layout = (SmoothAppBarLayout) child;
                setScrollTargetCallback(layout.mScrollTargetCallback);
                vViewPager = layout.vViewPager;
                if (vViewPager != null) {
                    vViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                        @Override
                        public void onPageScrollStateChanged(int state) {
                        }

                        @Override
                        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                        }

                        @Override
                        public void onPageSelected(int position) {
                            propagateViewPagerOffset(layout, true);
                        }
                    });
                }

                layout.setSyncOffsetListener(new com.zzc.androidtrain.view.refresh.OnOffsetChangedListener() {
                    @Override
                    public void onOffsetChanged(SmoothAppBarLayout smoothAppBarLayout, int verticalOffset,
                            boolean isOrientationChanged) {
                        syncOffset(smoothAppBarLayout, -verticalOffset);
                        if (!isOrientationChanged) {
                            propagateViewPagerOffset(smoothAppBarLayout, false);
                        }
                    }
                });
            }
        }

        @Override
        protected void onScrollChanged(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int y,
                int dy, boolean accuracy) {
            if (!mScrollFlag.isFlagScrollEnabled() || !(child instanceof SmoothAppBarLayout)) {
                return;
            }

            int targetId = ((SmoothAppBarLayout) child).mTargetId;
            if (targetId > 0 && targetId != target.getId()) {
                return;
            }

            if (vViewPager != null && vViewPager.getAdapter() instanceof ObservablePagerAdapter) {
                ObservablePagerAdapter pagerAdapter = (ObservablePagerAdapter) vViewPager.getAdapter();
                if (pagerAdapter.getObservableFragment(vViewPager.getCurrentItem()).getScrollTarget() != target) {
                    return;
                }
            }

            // TODO: temporary fix for issues:
            // https://github.com/henrytao-me/smooth-app-bar-layout/issues/114
            // https://github.com/henrytao-me/smooth-app-bar-layout/issues/139
            if (y == mLastY) {
                return;
            }
            mLastY = y;

            int oDy = dy;
            int minOffset = getMinOffset(child);
            int maxOffset = getMaxOffset(child);
            int translationOffset = accuracy ? Math.min(Math.max(minOffset, -y), maxOffset) : minOffset;

            dy = dy != 0 ? dy : y + getCurrentOffset();

            if (mScrollFlag.isQuickReturnEnabled()) {
                translationOffset = getCurrentOffset() - dy;
                translationOffset = Math.min(Math.max(minOffset, translationOffset), maxOffset);
                int breakPoint = minOffset + getMinHeight(child, true);
                if (dy <= 0 && !(accuracy && y <= Math.abs(breakPoint))) {
                    translationOffset = Math.min(translationOffset, breakPoint);
                }
                // TODO: temporary fix for issue https://github.com/henrytao-me/smooth-app-bar-layout/issues/108
                translationOffset = !accuracy && oDy == 0 && getCurrentOffset() == minOffset ? minOffset
                        : translationOffset;
            } else if (mScrollFlag.isFlagEnterAlwaysEnabled()) {
                translationOffset = getCurrentOffset() - dy;
                translationOffset = Math.min(Math.max(minOffset, translationOffset), maxOffset);
            } else if (mScrollFlag.isFlagEnterAlwaysCollapsedEnabled()) {
                // do nothing
            } else if (mScrollFlag.isFlagExitUntilCollapsedEnabled()) {
                // do nothing
            }

            Utils.log("widget | onScrollChanged | %d | %d | %d | %d | %d | %b | %d", minOffset, maxOffset,
                    getCurrentOffset(), y, dy, accuracy, translationOffset);
            syncOffset(child, translationOffset);

            propagateViewPagerOffset((SmoothAppBarLayout) child, false);
        }

        protected int getMaxOffset(AppBarLayout layout) {
            return 0;
        }

        protected int getMinOffset(AppBarLayout layout) {
            int minOffset = layout.getMeasuredHeight();
            if (mScrollFlag != null) {
                if (mScrollFlag.isFlagScrollEnabled()) {
                    minOffset = layout.getMeasuredHeight() - getMinHeight(layout, false);
                }
            }
            if (ViewCompat.getFitsSystemWindows(layout)) {
                if (mStatusBarSize == 0) {
                    mStatusBarSize = Utils.getStatusBarSize(layout.getContext());
                }
                minOffset -= mStatusBarSize;
            }
            return -Math.max(minOffset, 0);
        }

        private int getMinHeight(AppBarLayout layout, boolean forceQuickReturn) {
            int minHeight = ViewCompat.getMinimumHeight(layout);
            if (mScrollFlag.isFlagExitUntilCollapsedEnabled()
                    || (minHeight > 0 && !mScrollFlag.isQuickReturnEnabled()) || forceQuickReturn) {
                return minHeight > 0 ? minHeight : ViewCompat.getMinimumHeight(mScrollFlag.getView());
            }
            return 0;
        }

        private boolean propagateViewPagerOffset(SmoothAppBarLayout smoothAppBarLayout, int position) {
            if (vViewPager != null && vViewPager.getAdapter() instanceof ObservablePagerAdapter) {
                int n = vViewPager.getAdapter().getCount();
                if (position >= 0 && position < n) {
                    int currentItem = vViewPager.getCurrentItem();
                    int currentOffset = Math.max(0, -getCurrentOffset());
                    Utils.log("widget | propagateViewPagerOffset | %d | %d | %d", currentItem, position,
                            currentOffset);

                    try {
                        ObservablePagerAdapter pagerAdapter = (ObservablePagerAdapter) vViewPager.getAdapter();
                        ObservableFragment fragment = pagerAdapter.getObservableFragment(position);
                        View target = pagerAdapter.getObservableFragment(currentItem).getScrollTarget();

                        return fragment.onOffsetChanged(smoothAppBarLayout, target, currentOffset);
                    } catch (Exception ex) {
                        Log.e("SmoothAppBarLayout",
                                String.format(Locale.US, "ViewPager at position %d and %d need to implement %s",
                                        currentItem, position, ObservableFragment.class.getName()));
                    }
                }
            }
            return true;
        }

        private void propagateViewPagerOffset(SmoothAppBarLayout smoothAppBarLayout, boolean isOnPageSelected) {
            if (vViewPager != null) {
                Utils.log("widget | propagateViewPagerOffset | isPageSelected | %b", isOnPageSelected);

                int currentItem = vViewPager.getCurrentItem();
                boolean shouldPropagate = true;
                if (isOnPageSelected) {
                    shouldPropagate = propagateViewPagerOffset(smoothAppBarLayout, currentItem);
                }

                if (shouldPropagate) {
                    int n = vViewPager.getAdapter().getCount();
                    for (int i = 0; i < n; i++) {
                        if (i != currentItem) {
                            propagateViewPagerOffset(smoothAppBarLayout, i);
                        }
                    }
                }
            }
        }
    }
}