淘宝上拉查看图文详情效果

**

一、需要注意的地方是:

**

1、如果是手动滑到底部需要再次按下才能继续往下滑,自动滚动到底部则不需要

2、在由上一个ScrollView滑动到下一个ScrollView的过程中多只手指相继拖动也不会导致布局的剧变,也就是多个pointer的滑动不会导致move距离的剧变。

这个Layout的实现思路是:

在布局中放置两个ScrollView,并为其设置OnTouchListener,时刻判断ScrollView的滚动距离,一旦第一个ScrollView滚动到底部,则标识改为可向上拖动,此时开始记录滑动距离mMoveLen,根据mMoveLen重新layout两个ScrollView;同理,监听第二个ScrollView是否滚动到顶部,以往下拖动。

**

二、涉及到的知识点小结:

**
1、Timer和TimerTask

Timer是一种定时器工具,用来在一个后台线程计划执行指定任务。它可以计划执行一个任务一次或反复多次。
TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。

2、VelocityTracker

VelocityTracker顾名思义即速度跟踪,在Android中主要应用于touch event, VelocityTracker通过跟踪一连串事件实时计算出当前的速度,这样的用法在android系统空间中随处可见,比如Gestures中的Fling, Scrolling等。

3、handler.obtainMessage().sendToTarget()

使用的是sendToTarget()而不是sendMessage(),sendToTarget()方法并不是真正的将Message发送出去,而是将Message压入了MessageQueue中,真正去回调handleMessage还是在Looper.loop()中完成的。

**

三、核心代码

**
1、自定义ScrollView类

package com.trthi.mall.components;

import java.util.Timer;
import java.util.TimerTask;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.ScrollView;

import com.trthi.mall.listeners.ScrollChangedListener;

/**
 * 包含两个ScrollView的容器
 *
 * 
 */
public class ScrollViewContainer extends RelativeLayout {

    /**
     * 自动上滑
     */
    public static final int AUTO_UP = 0;
    /**
     * 自动下滑
     */
    public static final int AUTO_DOWN = 1;
    /**
     * 动画完成
     */
    public static final int DONE = 2;
    /**
     * 动画速度
     */
    public static final float SPEED = 8f;

    private boolean isMeasured = false;

    /**
     * 用于计算手滑动的速度
     */
    private VelocityTracker vt;

    private int mViewHeight;
    private int mViewWidth;

    private View topView;
    private View bottomView;

    private boolean canPullDown;
    private boolean canPullUp;
    private int state = DONE;

    /**
     * 记录当前展示的是哪个view,0是topView,1是bottomView
     */
    private int mCurrentViewIndex = 0;

    public int getmCurrentViewIndex(){
        return mCurrentViewIndex;
    }

    /**
     * 手滑动距离,这个是控制布局的主要变量
     */
    private float mMoveLen;
    private MyTimer mTimer;
    private float mLastY;
    /**
     * 用于控制是否变动布局的另一个条件,mEvents==0时布局可以变动
     */
    private int mEvents;

    private ScrollChangedListener scrollChangedListener = null;

    public void setScrollChangedListener(ScrollChangedListener scrollChangedListener){
        this.scrollChangedListener = scrollChangedListener;
    }

    private Handler handler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            if (mMoveLen != 0) {
                if (state == AUTO_UP) {
                    mMoveLen -= SPEED;
                    if (mMoveLen <= -mViewHeight) {
                        mMoveLen = -mViewHeight;
                        state = DONE;
                        mCurrentViewIndex = 1;
                        if (scrollChangedListener != null) {
                            scrollChangedListener.onScrollChanged(mCurrentViewIndex);
                        }
                    }
                } else if (state == AUTO_DOWN) {
                    mMoveLen += SPEED;
                    if (mMoveLen >= 0) {
                        mMoveLen = 0;
                        state = DONE;
                        mCurrentViewIndex = 0;
                        if (scrollChangedListener != null) {
                            scrollChangedListener.onScrollChanged(mCurrentViewIndex);
                        }
                    }
                } else {
                    mTimer.cancel();
                }
            }
            requestLayout();
        }

    };

    public ScrollViewContainer(Context context) {
        this(context,null);

    }

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

    public ScrollViewContainer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        mTimer = new MyTimer(handler);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                try {
                    if (vt == null)
                        vt = VelocityTracker.obtain();
                    else
                        vt.clear();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                mLastY = ev.getY();
                vt.addMovement(ev);
                mEvents = 0;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
            case MotionEvent.ACTION_POINTER_UP:
                // 多一只手指按下或抬起时舍弃将要到来的第一个事件move,防止多点拖拽的bug
                mEvents = -1;
                break;
            case MotionEvent.ACTION_MOVE:
                vt.addMovement(ev);
                if (canPullUp && mCurrentViewIndex == 0 && mEvents == 0) {
                    mMoveLen += (ev.getY() - mLastY);
                    // 防止上下越界
                    if (mMoveLen > 0) {
                        mMoveLen = 0;
                        mCurrentViewIndex = 0;
                    } else if (mMoveLen < -mViewHeight) {
                        mMoveLen = -mViewHeight;
                        mCurrentViewIndex = 1;

                    }
                    if (mMoveLen < -8) {
                        // 防止事件冲突
                        ev.setAction(MotionEvent.ACTION_CANCEL);
                    }
                } else if (canPullDown && mCurrentViewIndex == 1 && mEvents == 0) {
                    mMoveLen += (ev.getY() - mLastY);
                    // 防止上下越界
                    if (mMoveLen < -mViewHeight) {
                        mMoveLen = -mViewHeight;
                        mCurrentViewIndex = 1;
                    } else if (mMoveLen > 0) {
                        mMoveLen = 0;
                        mCurrentViewIndex = 0;
                    }
                    if (mMoveLen > 8 - mViewHeight) {
                        // 防止事件冲突
                        ev.setAction(MotionEvent.ACTION_CANCEL);
                    }
                } else
                    mEvents++;
                mLastY = ev.getY();
                requestLayout();
                break;
            case MotionEvent.ACTION_UP:
                mLastY = ev.getY();
                vt.addMovement(ev);
                vt.computeCurrentVelocity(700);
                // 获取Y方向的速度
                float mYV = vt.getYVelocity();
                if (mMoveLen == 0 || mMoveLen == -mViewHeight)
                    break;
                if (Math.abs(mYV) < 500) {
                    // 速度小于一定值的时候当作静止释放,这时候两个View往哪移动取决于滑动的距离
                    if (mMoveLen <= -mViewHeight / 2) {
                        state = AUTO_UP;
                    } else if (mMoveLen > -mViewHeight / 2) {
                        state = AUTO_DOWN;
                    }
                } else {
                    // 抬起手指时速度方向决定两个View往哪移动
                    if (mYV < 0)
                        state = AUTO_UP;
                    else
                        state = AUTO_DOWN;
                }
                mTimer.schedule(2);
                break;

        }
        super.dispatchTouchEvent(ev);
        return true;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (!isMeasured) {
            isMeasured = true;

            mViewHeight = getMeasuredHeight();
            mViewWidth = getMeasuredWidth();

            topView = getChildAt(0);
            bottomView = getChildAt(1);

            bottomView.setOnTouchListener(bottomViewTouchListener);
            topView.setOnTouchListener(topViewTouchListener);
        }
        topView.layout(0, (int) mMoveLen, mViewWidth,
                topView.getMeasuredHeight() + (int) mMoveLen);
        bottomView.layout(0, topView.getMeasuredHeight() + (int) mMoveLen,
                mViewWidth, topView.getMeasuredHeight() + (int) mMoveLen
                        + bottomView.getMeasuredHeight());
    }

    private OnTouchListener topViewTouchListener = new OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            ScrollView sv = (ScrollView) v;
            if (sv.getScrollY() == (sv.getChildAt(0).getMeasuredHeight() - sv
                    .getMeasuredHeight()) && mCurrentViewIndex == 0)
                canPullUp = true;
            else
                canPullUp = false;
            return false;
        }
    };
    private OnTouchListener bottomViewTouchListener = new OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            ScrollView sv = (ScrollView) v;
            if (sv.getScrollY() == 0 && mCurrentViewIndex == 1)
                canPullDown = true;
            else
                canPullDown = false;
            return false;
        }
    };

    class MyTimer {
        private Handler handler;
        private Timer timer;
        private MyTask mTask;

        public MyTimer(Handler handler) {
            this.handler = handler;
            timer = new Timer();
        }

        public void schedule(long period) {
            if (mTask != null) {
                mTask.cancel();
                mTask = null;
            }
            mTask = new MyTask(handler);
            timer.schedule(mTask, 0, period);
        }

        public void cancel() {
            if (mTask != null) {
                mTask.cancel();
                mTask = null;
            }
        }

        class MyTask extends TimerTask {
            private Handler handler;

            public MyTask(Handler handler) {
                this.handler = handler;
            }

            @Override
            public void run() {
                handler.obtainMessage().sendToTarget();
            }

        }
    }

}

2、XML文件

<com.trthi.mall.components.ScrollViewContainer      xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
  tools:context="com.trthi.mall.fragments.ProductDetailFragment">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            
             <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="上拉显示图文详情"
                    android:textSize="16sp" />

        LinearLayout>
    ScrollView>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <WebView
                android:id="@+id/webView"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

            WebView>
        LinearLayout>
    ScrollView>
com.trthi.mall.components.ScrollViewContainer>

在activity中直接用setViewContent(R.layout.~)就可以显示效果

3、扩展:

根据需求,滑到底部有一个提示条,就像TextView一样,显示上拉显示图文详情之类的。但是当滑到下半部分时,在要向上滑显示的文字因该变为下拉显示商品详情之类的。这时就要利用到接口回调。

1)定义接口

public interface ScrollChangedListener {
    //mCurrentViewIndex 表示当前是上半部分还是下半部分
    void onScrollChanged(int mCurrentViewIndex);
}

2)在自定义的ScrollView中设置回调的方法

 private ScrollChangedListener scrollChangedListener = null;

 public void setScrollChangedListener(ScrollChangedListener scrollChangedListener){

        this.scrollChangedListener = scrollChangedListener;
    }

以及在handler处的使用:
if (scrollChangedListener != null) {
                            scrollChangedListener.onScrollChanged(mCurrentViewIndex);
                        }

3)在activity中要实现接口(即implements ScrollChangedListener ),然后重写抽象方法

 @Override
    public void onScrollChanged(int state) {
        if(state == 0){
            //TODO 修改textview的文字及图片方向
            ((TextView)mGoToPicView.getChildAt(1)).setText("上拉查看图文详情");
        }else {
            ((TextView)mGoToPicView.getChildAt(1)).setText("下滑查看商品详情");
        }
    }

一定不要忘了设置监听,

mScrollView = (ScrollViewContainer) findViewById(R.id.scrollview);

mScrollView.setScrollChangedListener(this);

你可能感兴趣的:(android)