Android自定义控件实战——仿淘宝商品浏览界面

 转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38656929    

用手机淘宝浏览商品详情时,商品图片是放在后面的,在第一个ScrollView滚动到最底下时会有提示,继续拖动才能浏览图片。仿照这个效果写一个出来并不难,只要定义一个Layout管理两个ScrollView就行了,当第一个ScrollView滑到底部时,再次向上滑动进入第二个ScrollView。效果如下:

技术分享

需要注意的地方是:

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

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

这个Layout的实现思路是:

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

OK,明白了原理之后可以看代码了:

  

 
package com.jingchen.tbviewer;  
  
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.view.MotionEvent;  
import android.view.VelocityTracker;  
import android.view.View;  
import android.widget.RelativeLayout;  
import android.widget.ScrollView;  
  
/** 
 * 包含两个ScrollView的容器 
 *  
 * @author chenjing 
 *  
 */  
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 = 6.5f;  
  
    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;  
    /** 
     * 手滑动距离,这个是控制布局的主要变量 
     */  
    private float mMoveLen;  
    private MyTimer mTimer;  
    private float mLastY;  
    /** 
     * 用于控制是否变动布局的另一个条件,mEvents==0时布局可以拖拽了,mEvents==-1时可以舍弃将要到来的第一个move事件, 
     * 这点是去除多点拖动剧变的关键 
     */  
    private int mEvents;  
  
    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;  
                    }  
                } else if (state == AUTO_DOWN) {  
                    mMoveLen += SPEED;  
                    if (mMoveLen >= 0) {  
                        mMoveLen = 0;  
                        state = DONE;  
                        mCurrentViewIndex = 0;  
                    }  
                } else {  
                    mTimer.cancel();  
                }  
            }  
            requestLayout();  
        }  
  
    };  
  
    public ScrollViewContainer(Context context) {  
        super(context);  
        init();  
    }  
  
    public ScrollViewContainer(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        init();  
    }  
  
    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:  
            if (vt == null)  
                vt = VelocityTracker.obtain();  
            else  
                vt.clear();  
            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);  
            try {  
                vt.recycle();  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            break;  
  
        }  
        super.dispatchTouchEvent(ev);  
        return true;  
    }  
  
    @Override  
    protected void onLayout(boolean changed, int l, int t, int r, int b) {  
        topView.layout(0, (int) mMoveLen, mViewWidth,  
                topView.getMeasuredHeight() + (int) mMoveLen);  
        bottomView.layout(0, topView.getMeasuredHeight() + (int) mMoveLen,  
                mViewWidth, topView.getMeasuredHeight() + (int) mMoveLen  
                        + bottomView.getMeasuredHeight());  
    }  
  
    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
        if (!isMeasured) {  
            isMeasured = true;  
  
            mViewHeight = getMeasuredHeight();  
            mViewWidth = getMeasuredWidth();  
  
            topView = getChildAt(0);  
            bottomView = getChildAt(1);  
  
            bottomView.setOnTouchListener(bottomViewTouchListener);  
            topView.setOnTouchListener(topViewTouchListener);  
        }  
    }  
  
    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();  
            }  
  
        }  
    }  
  
}  

 


注释写的很清楚了,有几个关键点需要讲一下:

 

    1、由于这里为两个ScrollView设置了OnTouchListener,所以在其他地方不能再设置了,否则就白搭了。

    2、两个ScrollView的layout参数统一由mMoveLen决定。

    3、变量mEvents有两个作用:一是防止手动滑到底部或顶部时继续滑动而改变布局,必须再次按下才能继续滑动;二是在新的pointer down或up时把mEvents设置成-1可以舍弃将要到来的第一个move事件,防止mMoveLen出现剧变。为什么会出现剧变呢?因为假设一开始只有一只手指在滑动,记录的坐标值是这个pointer的事件坐标点,这时候另一只手指按下了导致事件又多了一个pointer,这时候到来的move事件的坐标可能就变成了新的pointer的坐标,这时计算与上一次坐标的差值就会出现剧变,变化的距离就是两个pointer间的距离。所以要把这个move事件舍弃掉,让mLastY值记录这个pointer的坐标再开始计算mMoveLen。pointer up的时候也一样。

理解了这几点,看起来就没什么难度了,代码量也很小。

MainActivity的布局:

  

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
  
    <com.jingchen.tbviewer.ScrollViewContainer  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" >  
  
        <ScrollView  
            android:layout_width="match_parent"  
            android:layout_height="match_parent" >  
  
            <RelativeLayout  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content" >  
  
                <LinearLayout  
                    android:id="@+id/imagesLayout"  
                    android:layout_width="match_parent"  
                    android:layout_height="wrap_content"  
                    android:gravity="center_horizontal"  
                    android:orientation="vertical" >  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/h" />  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/i" />  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/j" />  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/k" />  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/l" />  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/m" />  
                </LinearLayout>  
  
                <TextView  
                    android:layout_width="match_parent"  
                    android:layout_height="60dp"  
                    android:layout_below="@id/imagesLayout"  
                    android:background="#eeeeee"  
                    android:gravity="center"  
                    android:text="继续拖动,查看更多美女"  
                    android:textSize="20sp" />  
            </RelativeLayout>  
        </ScrollView>  
  
        <ScrollView  
            android:layout_width="match_parent"  
            android:layout_height="match_parent"  
            android:background="#000000" >  
  
            <LinearLayout  
                android:layout_width="match_parent"  
                android:layout_height="match_parent"  
                android:gravity="center_horizontal"  
                android:orientation="vertical" >  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/a" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/b" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/c" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/d" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/e" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/f" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/g" />  
            </LinearLayout>  
        </ScrollView>  
    </com.jingchen.tbviewer.ScrollViewContainer>  
  
</RelativeLayout>  

 

 

在ScrollView中放了几张图片而已。

 

MainActivity的代码:

  

package com.jingchen.tbviewer;  
  
import android.app.Activity;  
import android.os.Bundle;  
import android.view.Menu;  
  
public class MainActivity extends Activity  
{  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
    }  
  
    @Override  
    public boolean onCreateOptionsMenu(Menu menu)  
    {  
        getMenuInflater().inflate(R.menu.main, menu);  
        return true;  
    }  
  
}  

 

啥也没有......

 

好了,到此结束~

源码下载

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。