• 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;  
        }  
      
    }  

    啥也没有......

    好了,到此结束~

    源码下载

  • 相关阅读:
    2星|《丰田一页纸极简思考法》:僵化、不易扩展、修改,价值不大
    4星|《亿万》:FBI大战华尔街对冲基金大鳄
    3星|《CMO到底能干多久?》:CEO必须决定供公司需要哪类CMO
    中老年创业成功率更高,美国投资人的创业指南:4星|《烧掉你的商业计划书》
    3星|《科技投资新时代》:TMT行业资讯汇编
    2星|史蒂芬·平克《风格感觉》:英语写作指导,不适合外行阅读
    3星|《故事课1》:7个步骤+36种情节+悬念,作者没有拿得出手的故事作品
    2.5星|《数字货币:世界为我疯狂》:5年来《商业周刊》的区块链与比特币相关的文章16篇
    2.5星|《哈佛商学院管理与MBA案例全书》:书名太唬人了,依据中文经管书汇编整理而成
    3.5星|《第一本经济学》:通俗、可信的经济学入门
  • 原文地址:https://www.cnblogs.com/niray/p/4276912.html
Copyright © 2020-2023  润新知