个人主页常见的头像与背景图不同步移动的下拉效果实现

来源:互联网 发布:成都电视台网络直播 编辑:程序博客网 时间:2024/06/06 00:31

前言

我自己想出来的实现方式,而且我觉得这样实现效率最高。

效果图

这里写图片描述

原理

  1. 假设背景图比用户信息视图高x,那么将背景的顶部x/2和底部x/2隐藏起来,如图所示
    这里写图片描述
  2. 下拉的时候,让背景的移动速度是内容移动速度的1/2
  3. 下拉到一定程度,背景完全显示之后,让背景和内容的移动速度保持一致

如何隐藏背景图的顶部和底部?
通过设置背景的marginTop为-x/2隐藏顶部,而个人资料视图下面的视图会把背景图底部x/2挡住。

为什么要让背景的移动速度是内容移动速度的1/2?
这样的话,背景的顶部和底部都会慢慢可见,效果更佳。顶部慢慢可见是因为背景有向下的移动速度。底部慢慢可见是因为背景向下的移动速度小于内容的向下移动速度。

Demo

Github

布局

<com.example.myronlg.asyncscrollviewdemo.AsyncScrollFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/asv"    android:layout_width="match_parent"    android:layout_height="wrap_content"    tools:context=".MainActivity">    <!-- 背景 -->    <ImageView        android:layout_width="match_parent"        android:layout_height="match_parent"        android:scaleType="centerCrop"        android:src="@drawable/bg" />    <!-- 内容 -->    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="vertical">        <!-- 用户信息 -->        <LinearLayout            android:id="@+id/user_info_container"            android:layout_width="match_parent"            android:layout_height="@dimen/bg_visual_height"            android:gravity="center"            android:orientation="vertical">            <de.hdodenhof.circleimageview.CircleImageView                android:id="@+id/potrait"                android:layout_width="64dp"                android:layout_height="64dp"                android:src="@drawable/potrait" />            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="myron.loop"                android:textColor="@android:color/primary_text_dark" />        </LinearLayout>        <com.astuetz.PagerSlidingTabStrip            android:id="@+id/tv_anchor"            android:layout_width="match_parent"            android:layout_height="@dimen/anchor_height"            android:layout_below="@+id/user_info_container"            android:background="@android:color/white"            />        <android.support.v4.view.ViewPager            android:id="@+id/vp"            android:layout_width="match_parent"            android:layout_height="1000dp"            android:layout_below="@+id/tv_anchor"            android:visibility="visible" />    </LinearLayout></com.example.myronlg.asyncscrollviewdemo.AsyncScrollFrameLayout>

关键源码

隐藏背景的顶部和底部

        float bgDrawableHeight = getBgDrawableHeight();        float bgDrawableVisibleHeight = userInfoView.getMeasuredHeight();        bgDrawableHiddenHeight = (int) (bgDrawableHeight - bgDrawableVisibleHeight);        FrameLayout.LayoutParams bgViewLayoutParams = (LayoutParams) bgView.getLayoutParams();        bgViewLayoutParams.setMargins(0, -bgDrawableHiddenHeight/2, 0, 0);

移动背景和内容

                if (fgView.getScrollY() <= -bgDrawableHiddenHeight) {//if the bgView is shown entirely, sync scroll                    scrollBy(0, computeDy(-dy * 0.5F));                } else {//if the bgView is not shown entirely, async scroll                    fgView.scrollBy(0, computeDy(-dy * 0.5F));                    bgView.scrollBy(0, computeDy(-dy * 0.25F));                }

完整源码

public class AsyncScrollFrameLayout extends FrameLayout {    private View bgView;    private View fgView;    private ViewPager viewPager;    private float lastY = -1;    private Scroller scroller;    /**     * A value that indicate who should scroll, this frameLayout or inner scrollable view.     * If the inner scrollable view is entirely visible, let it scroll. Otherwise, let this frameLayout scroll.     */    private int interceptThreshold;    private boolean intercept;    private int bgDrawableHiddenHeight;    public AsyncScrollFrameLayout(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        init();    }    public AsyncScrollFrameLayout(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public AsyncScrollFrameLayout(Context context) {        super(context);        init();    }    private void init() {        scroller = new Scroller(getContext(), new AccelerateDecelerateInterpolator());    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        bgView = getChildAt(0);        fgView = getChildAt(1);        View userInfoView = ((ViewGroup) fgView).getChildAt(0);        View anchorView = ((ViewGroup) fgView).getChildAt(1);        viewPager = (ViewPager) ((ViewGroup) fgView).getChildAt(2);        interceptThreshold = anchorView.getTop();        float bgDrawableHeight = getBgDrawableHeight();        float bgDrawableVisibleHeight = userInfoView.getMeasuredHeight();        bgDrawableHiddenHeight = (int) (bgDrawableHeight - bgDrawableVisibleHeight);        FrameLayout.LayoutParams bgViewLayoutParams = (LayoutParams) bgView.getLayoutParams();        bgViewLayoutParams.setMargins(0, -bgDrawableHiddenHeight/2, 0, 0);        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) viewPager.getLayoutParams();        layoutParams.height = getMeasuredHeight() - anchorView.getMeasuredHeight();        viewPager.setLayoutParams(layoutParams);    }    private float getBgDrawableHeight() {        ImageView bgImageView = (ImageView) bgView;        Drawable bgDrawable = bgImageView.getDrawable();        return ((float) bgDrawable.getIntrinsicHeight()) * bgImageView.getMeasuredWidth() / bgDrawable.getIntrinsicWidth();    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        return handleTouchEvent(ev);    }/*    不要去重写这两个函数,想要完全掌控触摸事件的传递和处理你必须要重写dispatchTouchEvent(),而重写dispatchTouchEvent就足够完全掌控触摸事件的传递和处理    因为这里涉及一个手势的前半部分由一个控件处理,后半部分由另一个控件处理的情况,Android事件处理框架是把一个手势交给一个控件处理的。    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        if (getScrollY() < interceptThreshold) {            outerScrolling = true;            return true;        } else {            if (outerScrolling) {                dispathDownEventMannualy();            }        }        return super.onInterceptTouchEvent(ev);        return intercept;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        return true;    }*/    private boolean handleTouchEvent(MotionEvent ev) {        if (lastY == -1) {            lastY = ev.getY();        }        boolean flag = false;        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                flag = onDown(ev);                break;            case MotionEvent.ACTION_MOVE:                flag = onMove(ev);                break;            case MotionEvent.ACTION_UP:            case MotionEvent.ACTION_CANCEL:                flag = onEnd(ev);                break;            default:        }        lastY = ev.getY();        return flag;    }    private boolean onDown(MotionEvent ev) {        super.dispatchTouchEvent(ev);        return true;    }    private boolean onMove(MotionEvent ev) {        float dy = ev.getY() - lastY;        //this container take care of scrolling        if (fgView.getScrollY() < interceptThreshold) {            // pull down over scroll            if (fgView.getScrollY() < 0 || (fgView.getScrollY() == 0 && dy > 0)) {                if (fgView.getScrollY() <= -bgDrawableHiddenHeight) {//if the bgView is shown entirely, sync scroll                    scrollBy(0, computeDy(-dy * 0.5F));                } else {//if the bgView is not shown entirely, async scroll                    fgView.scrollBy(0, computeDy(-dy * 0.5F));                    bgView.scrollBy(0, computeDy(-dy * 0.25F));                }            } else {//pull up                int scrollYDelta = computeDy(-dy);                if (fgView.getScrollY()+scrollYDelta < 0){                    // let async scroll take care from here                    scrollBy(0, -fgView.getScrollY());                } else if (fgView.getScrollY()+scrollYDelta >= interceptThreshold) {                    // let bottom scrollable view take care from here                    scrollBy(0, interceptThreshold -fgView.getScrollY());                } else {                    scrollBy(0, scrollYDelta);                }            }            intercept = true;            return true;        } else {           if (isViewPagerReachTop() && dy > 0){               if (!intercept) {                   fgView.setScrollY(interceptThreshold);                   bgView.setScrollY(interceptThreshold);                   dispatchCancelEvent(ev);               }               int scrollYDelta = computeDy(-dy);               scrollBy(0, scrollYDelta);//               this is 99% impossible//               if (fgView.getScrollY()+scrollYDelta < 0){//                   scrollBy(0, -fgView.getScrollY());//               } else {//                   scrollBy(0, scrollYDelta);//               }               intercept = true;               return true;           } else {               if (intercept){                   dispatchDownEvent(ev);               }               intercept = false;               return super.dispatchTouchEvent(ev);           }        }    }    private void dispatchCancelEvent(MotionEvent ev){        MotionEvent cancelEvent = MotionEvent.obtain(ev);        cancelEvent.setAction(MotionEvent.ACTION_CANCEL);        super.dispatchTouchEvent(cancelEvent);    }    private void dispatchDownEvent(MotionEvent ev){        MotionEvent downEvent = MotionEvent.obtain(ev);        downEvent.setAction(MotionEvent.ACTION_DOWN);        super.dispatchTouchEvent(downEvent);    }    private boolean onEnd(MotionEvent ev) {        if (fgView.getScrollY() <= 0) {            scroller.startScroll(bgView.getScrollY(), fgView.getScrollY(), -bgView.getScrollY(), -fgView.getScrollY());            invalidate();        }        lastY = -1;        intercept = false;        return super.dispatchTouchEvent(ev);    }    private boolean isViewPagerReachTop() {        int currentIndex = viewPager.getCurrentItem();        View currentPage = viewPager.findViewWithTag(currentIndex);        return isViewReachTop(currentPage);    }    private boolean isViewReachTop(View view) {        if (view instanceof AdapterView<?>) {            AdapterView<?> adapterView = (AdapterView<?>) view;            if (adapterView.getLastVisiblePosition() == 0                    && adapterView.getChildAt(0).getTop() >= 0) {                return true;            } else {                return false;            }        } else {            if (view.getScrollY() <= 0) {                return true;            } else {                return false;            }        }    }    @Override    public void scrollBy(int x, int y) {        fgView.scrollBy(x, y);        bgView.scrollBy(x, y);    }    private int computeDy(float dy){        if (dy > 0){            return Math.round(dy+0.5F);        } else {            return Math.round(dy-0.5F);        }    }    @Override    public void computeScroll() {        if (scroller.computeScrollOffset()) {            fgView.scrollTo(0, scroller.getCurrY());            bgView.scrollTo(0, scroller.getCurrX());            postInvalidate();        } else {            super.computeScroll();        }    }}
1 0
原创粉丝点击