Android Scroll之仿微博微信实现滑动标题栏渐变效果

1 、简介

本文主要整理了一些scroll常用的知识点,并基于滑动知识,仿微博微信实现了滑动时标题栏渐变的运行效果。进一步增长了对滑动api的理解与运用的能力。

2、知识储备(关于scroll你所必须需要了解的)

讲到滑动,又不得不和滑动去打交道,滑动这块的 scrollTo() , scrollBy() , getScrollX()  getScrollY()啊这些真实比较难懂,小白我就不班门弄斧了,可以参考讲的比较好的博客  关于View的ScrollTo, getScrollX 和 getScrollY 。主要是需要掌握一个宏观的窗口与画布的关系,那个图是真的好懂。

重点:scrollTo(),scrollBy()

这2个API还是比较重要的,现在着重拎出来领悟一下。

不管是scrollTo()还是scrollBy()方法,滚动的都是该View内部的内容,是瞬间完成的(无法看到平移的效果)

说明: 如果滑动的是LinearLayout 那么实际上滑动的是其内部的内容,而如果滑动一个Button那么其实很可能结果会出乎我们的意料的。

   /**
     * The offset, in pixels, by which the content of this view is scrolled
     * horizontally.
     * {@hide}
     */
    @ViewDebug.ExportedProperty(category = "scrolling")
    protected int mScrollX;
    /**
     * The offset, in pixels, by which the content of this view is scrolled
     * vertically.
     * {@hide}
     */
    @ViewDebug.ExportedProperty(category = "scrolling")
    protected int mScrollY;
   
    
    /**
     * Set the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }


    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

    
    // 最终会调用的
    @Deprecated
    public void invalidate(int l, int t, int r, int b) {
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
    }

如果我们传递的scrollerX值是正数的话,(l - scrollX) 计算后则左边距会变小,所以内容会往左移动(也就是x轴的负方向)。如果我们传递的scrollerX值是负数的话,(l - scrollX) 计算后则左边距会变大,因此内容会往右移动(也就是x轴的正方向),同理,y轴也一样。
案例与运行结果:

 线性布局内含有一个Button按钮,执行了如下代码,线性布局的内容向右移动了100px。然后打印的log日志如下所示。 

        mLinearLayout = findViewById(R.id.llScrollLayout);
        Log.e(TAG, "getScrollX() = mScrollX ="+mLinearLayout.getScrollX() );
        mLinearLayout.scrollTo(-100,0);
        Log.e(TAG, "getScrollX() = mScrollY ="+mLinearLayout.getScrollX() );
        
        //E/MainActivity: getScrollX() =  getScrollX() = mScrollX   =  0
        //E/MainActivity: getScrollX() =  getScrollX() = mScrollX   = -100

Scroller类,平滑过渡

原理:Scroller类本身并没有实现view的滑动,它实际上需要配合View的computeScroller方法才能完成弹性滑动的效果,它不断地让View重绘,而每一次重绘距滑动的起始时间会有一个时间间隔,通过这个时间间隔Scroller类就可以得出View当前滑动的位置,知道了滑动位置就可以通过scrollTo()方法来完成View的滑动(这里也说明view的滑动最终还是要靠scrollTo()方法来实现,Scroller类本身本身也只是个辅助类而已),就这样,View每一次重绘都会导致View进行小幅度的滑动,而多次小幅度滑动的就组成了弹性滑动。

概述:  每隔一段小时时间重绘一次view(去调用scrollerTo()滑动),从而导致View进行了小幅度的滑动,次数多了,也就形成了平滑过渡 

Scroller.computeScrollOffset()结束返回false,未结束返回true

核心方法:

    /**
     * Called by a parent to request that a child update its values for mScrollX
     * and mScrollY if necessary. This will typically be done if the child is
     * animating a scroll using a {@link android.widget.Scroller Scroller}
     * object.
     * 推断滑动
     */
    public void computeScroll() {
    }

解释:当invalidate()或postInvalidate()都会导致这个方法的执行,也就是说view重绘时都会导致这个方法执行 

    /**
     * 覆写computeScroll
     * 在computeScroll()方法中
     *   在View的源码中可以看到public void computeScroll(){}是一个空方法.
     *   具体的实现需要自己来写.在该方法中我们可调用scrollTo()或scrollBy()
     *   来实现移动(动画).该方法才是实现移动的核心.
     *   4.1 利用Scroller的mScroller.computeScrollOffset()判断移动过程是否完成
     *       注意:该方法是Scroller中的方法而不是View中的!!!!!!
     *       public boolean computeScrollOffset(){ }
     *       Call this when you want to know the new location.
     *       If it returns true,the animation is not yet finished.
     *       loc will be altered to provide the new location.
     *       返回true时表示还移动还没有完成.
     *   4.2 若动画没有结束,则调用:scrollTo(By)();
     *       使其滑动scrolling
     *
     * 5 再次调用invalidate()或者postInvalidate();.
     *   调用invalidate()方法那么又会重绘View树.
     *   从而跳转到第3步,如此循环,便形成了动画移动的效果,直到computeScrollOffset返回false
     *
     *
     *  invalidate()与postInvalidate() 区别:
     * Invalidate the whole view. If the view is visible,
     * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
     * the future.
     * 

* This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. * 这段话的意思是: * invalidate()只能在UI线程调用而不能在非UI线程调用 * postInvalidate() 可以在非UI线程调用也可以在UI线程调用 */ @Override public void computeScroll() { if(scroller.computeScrollOffset()){ scrollTo(scroller.getCurrX(),scroller.getCurrY()); //重绘 postInvalidate(); } }

 简单的案例:参考资郭霖大神

Android Scroll之仿微博微信实现滑动标题栏渐变效果_第1张图片

/**
 * @author crazyZhangxl on 2018/11/5.
 * Describe: 一个简易的viewPager当然这是很不完善的
 *           主要用于了解 scroller的运用,包括onMeasure() onLayout() 对于子布局的测量和摆放
 */
public class SampleViewpager  extends ViewGroup{
    private Scroller mScroller;
    private int mTouchSlop;  // 最小滑动距离
    private float mMLastX;

    public SampleViewpager(Context context) {
        this(context,null);
    }

    public SampleViewpager(Context context, AttributeSet attrs) {
        this(context,attrs,0);
    }

    public SampleViewpager(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mScroller = new Scroller(context);
        mTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        for (int i=0;i mTouchSlop){
                    isIntercept = true;
                }
                mMLastX = moveRawX;
                break;
            default:
                break;
        }
        return isIntercept;
    }



    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                float moveRawX = event.getRawX();
                int scrollX = (int) (mMLastX - moveRawX);
                Log.e("结果", "onTouchEvent: "+scrollX );
                // 判断边界----
                scrollBy(scrollX,0);
                mMLastX = moveRawX;
                break;
            case MotionEvent.ACTION_UP:
                // 抬起的时候进行校验 进行回滚
                // 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面
                int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
                int dx = targetIndex * getWidth() - getScrollX();
                // 第二步,调用startScroll()方法来初始化滚动数据并刷新界面
                // 起始的位置(x,y)滑动的偏移量
                mScroller.startScroll(getScrollX(), 0, dx, 0);
                invalidate();
                break;
                default:
                    break;
        }

        return super.onTouchEvent(event);
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }
}

3.仿应用主界面滑动改变标题颜色demo

首先了解下ScrollView滑动的api

    /**
     * 滑动监听
     * This is called in response to an internal scroll in this view (i.e., the
     * view scrolled its own contents). This is typically as a result of
     * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
     * called.
     *
     * @param l Current horizontal scroll origin. 当前滑动的x轴距离
     * @param t Current vertical scroll origin. 当前滑动的y轴距离
     * @param oldl Previous horizontal scroll origin. 上一次滑动的x轴距离
     * @param oldt Previous vertical scroll origin. 上一次滑动的y轴距离
     */

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        Log.e("onScrollChanged","l = "+l+" ,t = "+t+" ,oldl = "+oldl+" ,oldt = "+oldt);
    }

以下图例为测量的控件的实际高度以及上滑和下滑的log日志打印

向上滑动,当前的滑动的y轴距离一直增加,也和我们的scrollTo相吻合。

Android Scroll之仿微博微信实现滑动标题栏渐变效果_第2张图片

Android Scroll之仿微博微信实现滑动标题栏渐变效果_第3张图片

 仿微博滑动标题栏渐变效果

Android Scroll之仿微博微信实现滑动标题栏渐变效果_第4张图片

重写NestedScrollView,设置垂直滑动的监听。 

public class ObservableAlphaScrollView extends NestedScrollView {
    private OnAlphaScrollChangeListener mOnAlphaScrollChangeListener;

    public ObservableAlphaScrollView(@NonNull Context context) {
        super(context);
    }

    public ObservableAlphaScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public ObservableAlphaScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setOnAlphaScrollChangeListener(OnAlphaScrollChangeListener onAlphaScrollChangeListener){
        this.mOnAlphaScrollChangeListener = onAlphaScrollChangeListener;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (mOnAlphaScrollChangeListener != null){
            mOnAlphaScrollChangeListener.onVerticalScrollChanged(t);
        }
    }

    public interface OnAlphaScrollChangeListener{
        void onVerticalScrollChanged(int t);
    }
}

 主体活动,测量渐变的距离,沉浸式状态栏设置状态栏偏移,设置滑动时状态改变。

public class ScrollAlphaActivity extends AppCompatActivity implements ObservableAlphaScrollView.OnAlphaScrollChangeListener {
    private ObservableAlphaScrollView mObservableAlphaScrollView;
    private View mTitleView;
    private TextView tvHeadView;
    private int statusBarHeight;
    private RelativeLayout mRlHead;
    private int moveDiatance;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scroll_alpha);
        // 设置的沉浸式状态栏
        mObservableAlphaScrollView = findViewById(R.id.llTest);
        mTitleView = findViewById(R.id.ll_header_content);
        tvHeadView = findViewById(R.id.tv_main_topContent);
        mRlHead = findViewById(R.id.rlHead);

        // 设置空余的Padiing
        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mRlHead.getLayoutParams();
        layoutParams.topMargin = getStatusBarHeight();
        mRlHead.setLayoutParams(layoutParams);


        // 测量高度差-----
        ViewTreeObserver treeObserver = tvHeadView.getViewTreeObserver();
        treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                // 移除监听
                tvHeadView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                int titleHeight = mTitleView.getMeasuredHeight();
                int headHeight = tvHeadView.getMeasuredHeight();
                moveDiatance = headHeight - titleHeight;
                mObservableAlphaScrollView.setOnAlphaScrollChangeListener(ScrollAlphaActivity.this);
            }
        });
    }

    @Override
    public void onVerticalScrollChanged(int t) {
        if (t<=0){
            mTitleView.setBackgroundColor(Color.argb(0, 48, 63, 159));
        }else {
            if (t 0) {
                result = getResources().getDimensionPixelSize(resourceId);
            }
            statusBarHeight = result;
        }
        return statusBarHeight;
    }
}

仿微信朋友圈渐进效果

Android Scroll之仿微博微信实现滑动标题栏渐变效果_第5张图片

/**
 * @author crazyZhangxl on 2018-11-5 16:49:54.
 * Describe: 仿微信朋友圈界面
 */

public class LikeWChatActivity extends AppCompatActivity implements ObservableAlphaScrollView.OnAlphaScrollChangeListener {
    private LinearLayout mLlTitle,mLlScHead;
    private int mTitleHeight;
    private int mHeadHeight;
    private int mDistance;
    private ObservableAlphaScrollView mScrollView;
    private ImageView mIvBack;
    private TextView mTvText;
    private int mDistanceY = 30;// 设置一个临界值吧

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_like_wchat);
        mLlTitle = findViewById(R.id.llTitle);
        mLlScHead = findViewById(R.id.llScHead);
        mScrollView = findViewById(R.id.scrollView);
        mIvBack = findViewById(R.id.imageBack);
        mTvText = findViewById(R.id.ivText);
        ViewTreeObserver viewTreeObserver = mLlScHead.getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mLlScHead.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                mTitleHeight = mLlTitle.getMeasuredHeight();
                mHeadHeight = mLlScHead.getMeasuredHeight();
                mDistance = mHeadHeight - mTitleHeight;
                Log.e("result  mTitleHead = ",mTitleHeight+"");
                Log.e("result  mHeadHeight = ",mHeadHeight+"");
                Log.e("result  mDistance = ",mDistance+"");
                mScrollView.setOnAlphaScrollChangeListener(LikeWChatActivity.this);
            }
        });

    }

    @Override
    public void onVerticalScrollChanged(int t) {
        if (t<= (mDistance - mDistanceY)){
            mTvText.setAlpha(0f);
            mIvBack.setSelected(false);
            mLlTitle.setBackgroundColor(Color.argb(0, 242, 242, 242));
        }else if (t<=mDistance) {
            mTvText.setAlpha(0f);
            mIvBack.setSelected(false);
        }else if (t <= (mDistance + mDistanceY)){
            mTvText.setAlpha(1f);
            mIvBack.setSelected(true);
            mLlTitle.setBackgroundColor(Color.argb(0, 242, 242, 242));
        }else {
            mTvText.setAlpha(1f);
            mIvBack.setSelected(true);
            mLlTitle.setBackgroundColor(Color.argb(255, 242, 242, 242));
        }
    }
}

 

参考博客:

Android Scroller完全解析,关于Scroller你所需知道的一切 

scroller类的用法完全解析以及带源码分析

高仿美团APP页面滑动标题栏渐变效果

项目地址:https://github.com/crazyzhangxl/ScrollDemo

 

你可能感兴趣的:(Android Scroll之仿微博微信实现滑动标题栏渐变效果)