当前位置:首页 > 开发教程 > 手机开发 >

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

时间:2016-09-01 08:52 来源:互联网 作者:源码搜藏 收藏

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

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

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

需要注意的地方是:

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

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

这个Layout的实现思路是:

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

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

 

[java] view plain copy
 
  1. package com.jingchen.tbviewer;  
  2.   
  3. import java.util.Timer;  
  4. import java.util.TimerTask;  
  5.   
  6. import android.content.Context;  
  7. import android.os.Handler;  
  8. import android.os.Message;  
  9. import android.util.AttributeSet;  
  10. import android.view.MotionEvent;  
  11. import android.view.VelocityTracker;  
  12. import android.view.View;  
  13. import android.widget.RelativeLayout;  
  14. import android.widget.ScrollView;  
  15.   
  16. /** 
  17.  * 包含两个ScrollView的容器 
  18.  *  
  19.  * @author chenjing 
  20.  *  
  21.  */  
  22. public class ScrollViewContainer extends RelativeLayout {  
  23.   
  24.     /** 
  25.      * 自动上滑 
  26.      */  
  27.     public static final int AUTO_UP = 0;  
  28.     /** 
  29.      * 自动下滑 
  30.      */  
  31.     public static final int AUTO_DOWN = 1;  
  32.     /** 
  33.      * 动画完成 
  34.      */  
  35.     public static final int DONE = 2;  
  36.     /** 
  37.      * 动画速度 
  38.      */  
  39.     public static final float SPEED = 6.5f;  
  40.   
  41.     private boolean isMeasured = false;  
  42.   
  43.     /** 
  44.      * 用于计算手滑动的速度 
  45.      */  
  46.     private VelocityTracker vt;  
  47.   
  48.     private int mViewHeight;  
  49.     private int mViewWidth;  
  50.   
  51.     private View topView;  
  52.     private View bottomView;  
  53.   
  54.     private boolean canPullDown;  
  55.     private boolean canPullUp;  
  56.     private int state = DONE;  
  57.   
  58.     /** 
  59.      * 记录当前展示的是哪个view,0是topView,1是bottomView 
  60.      */  
  61.     private int mCurrentViewIndex = 0;  
  62.     /** 
  63.      * 手滑动距离,这个是控制布局的主要变量 
  64.      */  
  65.     private float mMoveLen;  
  66.     private MyTimer mTimer;  
  67.     private float mLastY;  
  68.     /** 
  69.      * 用于控制是否变动布局的另一个条件,mEvents==0时布局可以拖拽了,mEvents==-1时可以舍弃将要到来的第一个move事件, 
  70.      * 这点是去除多点拖动剧变的关键 
  71.      */  
  72.     private int mEvents;  
  73.   
  74.     private Handler handler = new Handler() {  
  75.   
  76.         @Override  
  77.         public void handleMessage(Message msg) {  
  78.             if (mMoveLen != 0) {  
  79.                 if (state == AUTO_UP) {  
  80.                     mMoveLen -= SPEED;  
  81.                     if (mMoveLen <= -mViewHeight) {  
  82.                         mMoveLen = -mViewHeight;  
  83.                         state = DONE;  
  84.                         mCurrentViewIndex = 1;  
  85.                     }  
  86.                 } else if (state == AUTO_DOWN) {  
  87.                     mMoveLen += SPEED;  
  88.                     if (mMoveLen >= 0) {  
  89.                         mMoveLen = 0;  
  90.                         state = DONE;  
  91.                         mCurrentViewIndex = 0;  
  92.                     }  
  93.                 } else {  
  94.                     mTimer.cancel();  
  95.                 }  
  96.             }  
  97.             requestLayout();  
  98.         }  
  99.   
  100.     };  
  101.   
  102.     public ScrollViewContainer(Context context) {  
  103.         super(context);  
  104.         init();  
  105.     }  
  106.   
  107.     public ScrollViewContainer(Context context, AttributeSet attrs) {  
  108.         super(context, attrs);  
  109.         init();  
  110.     }  
  111.   
  112.     public ScrollViewContainer(Context context, AttributeSet attrs, int defStyle) {  
  113.         super(context, attrs, defStyle);  
  114.         init();  
  115.     }  
  116.   
  117.     private void init() {  
  118.         mTimer = new MyTimer(handler);  
  119.     }  
  120.   
  121.     @Override  
  122.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  123.         switch (ev.getActionMasked()) {  
  124.         case MotionEvent.ACTION_DOWN:  
  125.             if (vt == null)  
  126.                 vt = VelocityTracker.obtain();  
  127.             else  
  128.                 vt.clear();  
  129.             mLastY = ev.getY();  
  130.             vt.addMovement(ev);  
  131.             mEvents = 0;  
  132.             break;  
  133.         case MotionEvent.ACTION_POINTER_DOWN:  
  134.         case MotionEvent.ACTION_POINTER_UP:  
  135.             // 多一只手指按下或抬起时舍弃将要到来的第一个事件move,防止多点拖拽的bug  
  136.             mEvents = -1;  
  137.             break;  
  138.         case MotionEvent.ACTION_MOVE:  
  139.             vt.addMovement(ev);  
  140.             if (canPullUp && mCurrentViewIndex == 0 && mEvents == 0) {  
  141.                 mMoveLen += (ev.getY() - mLastY);  
  142.                 // 防止上下越界  
  143.                 if (mMoveLen > 0) {  
  144.                     mMoveLen = 0;  
  145.                     mCurrentViewIndex = 0;  
  146.                 } else if (mMoveLen < -mViewHeight) {  
  147.                     mMoveLen = -mViewHeight;  
  148.                     mCurrentViewIndex = 1;  
  149.   
  150.                 }  
  151.                 if (mMoveLen < -8) {  
  152.                     // 防止事件冲突  
  153.                     ev.setAction(MotionEvent.ACTION_CANCEL);  
  154.                 }  
  155.             } else if (canPullDown && mCurrentViewIndex == 1 && mEvents == 0) {  
  156.                 mMoveLen += (ev.getY() - mLastY);  
  157.                 // 防止上下越界  
  158.                 if (mMoveLen < -mViewHeight) {  
  159.                     mMoveLen = -mViewHeight;  
  160.                     mCurrentViewIndex = 1;  
  161.                 } else if (mMoveLen > 0) {  
  162.                     mMoveLen = 0;  
  163.                     mCurrentViewIndex = 0;  
  164.                 }  
  165.                 if (mMoveLen > 8 - mViewHeight) {  
  166.                     // 防止事件冲突  
  167.                     ev.setAction(MotionEvent.ACTION_CANCEL);  
  168.                 }  
  169.             } else  
  170.                 mEvents++;  
  171.             mLastY = ev.getY();  
  172.             requestLayout();  
  173.             break;  
  174.         case MotionEvent.ACTION_UP:  
  175.             mLastY = ev.getY();  
  176.             vt.addMovement(ev);  
  177.             vt.computeCurrentVelocity(700);  
  178.             // 获取Y方向的速度  
  179.             float mYV = vt.getYVelocity();  
  180.             if (mMoveLen == 0 || mMoveLen == -mViewHeight)  
  181.                 break;  
  182.             if (Math.abs(mYV) < 500) {  
  183.                 // 速度小于一定值的时候当作静止释放,这时候两个View往哪移动取决于滑动的距离  
  184.                 if (mMoveLen <= -mViewHeight / 2) {  
  185.                     state = AUTO_UP;  
  186.                 } else if (mMoveLen > -mViewHeight / 2) {  
  187.                     state = AUTO_DOWN;  
  188.                 }  
  189.             } else {  
  190.                 // 抬起手指时速度方向决定两个View往哪移动  
  191.                 if (mYV < 0)  
  192.                     state = AUTO_UP;  
  193.                 else  
  194.                     state = AUTO_DOWN;  
  195.             }  
  196.             mTimer.schedule(2);  
  197.             try {  
  198.                 vt.recycle();  
  199.             } catch (Exception e) {  
  200.                 e.printStackTrace();  
  201.             }  
  202.             break;  
  203.   
  204.         }  
  205.         super.dispatchTouchEvent(ev);  
  206.         return true;  
  207.     }  
  208.   
  209.     @Override  
  210.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  211.         topView.layout(0, (int) mMoveLen, mViewWidth,  
  212.                 topView.getMeasuredHeight() + (int) mMoveLen);  
  213.         bottomView.layout(0, topView.getMeasuredHeight() + (int) mMoveLen,  
  214.                 mViewWidth, topView.getMeasuredHeight() + (int) mMoveLen  
  215.                         + bottomView.getMeasuredHeight());  
  216.     }  
  217.   
  218.     @Override  
  219.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  220.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  221.         if (!isMeasured) {  
  222.             isMeasured = true;  
  223.   
  224.             mViewHeight = getMeasuredHeight();  
  225.             mViewWidth = getMeasuredWidth();  
  226.   
  227.             topView = getChildAt(0);  
  228.             bottomView = getChildAt(1);  
  229.   
  230.             bottomView.setOnTouchListener(bottomViewTouchListener);  
  231.             topView.setOnTouchListener(topViewTouchListener);  
  232.         }  
  233.     }  
  234.   
  235.     private OnTouchListener topViewTouchListener = new OnTouchListener() {  
  236.   
  237.         @Override  
  238.         public boolean onTouch(View v, MotionEvent event) {  
  239.             ScrollView sv = (ScrollView) v;  
  240.             if (sv.getScrollY() == (sv.getChildAt(0).getMeasuredHeight() - sv  
  241.                     .getMeasuredHeight()) && mCurrentViewIndex == 0)  
  242.                 canPullUp = true;  
  243.             else  
  244.                 canPullUp = false;  
  245.             return false;  
  246.         }  
  247.     };  
  248.     private OnTouchListener bottomViewTouchListener = new OnTouchListener() {  
  249.   
  250.         @Override  
  251.         public boolean onTouch(View v, MotionEvent event) {  
  252.             ScrollView sv = (ScrollView) v;  
  253.             if (sv.getScrollY() == 0 && mCurrentViewIndex == 1)  
  254.                 canPullDown = true;  
  255.             else  
  256.                 canPullDown = false;  
  257.             return false;  
  258.         }  
  259.     };  
  260.   
  261.     class MyTimer {  
  262.         private Handler handler;  
  263.         private Timer timer;  
  264.         private MyTask mTask;  
  265.   
  266.         public MyTimer(Handler handler) {  
  267.             this.handler = handler;  
  268.             timer = new Timer();  
  269.         }  
  270.   
  271.         public void schedule(long period) {  
  272.             if (mTask != null) {  
  273.                 mTask.cancel();  
  274.                 mTask = null;  
  275.             }  
  276.             mTask = new MyTask(handler);  
  277.             timer.schedule(mTask, 0, period);  
  278.         }  
  279.   
  280.         public void cancel() {  
  281.             if (mTask != null) {  
  282.                 mTask.cancel();  
  283.                 mTask = null;  
  284.             }  
  285.         }  
  286.   
  287.         class MyTask extends TimerTask {  
  288.             private Handler handler;  
  289.   
  290.             public MyTask(Handler handler) {  
  291.                 this.handler = handler;  
  292.             }  
  293.   
  294.             @Override  
  295.             public void run() {  
  296.                 handler.obtainMessage().sendToTarget();  
  297.             }  
  298.   
  299.         }  
  300.     }  
  301.   
  302. }  

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

 

    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的布局:

 

[html] view plain copy
 
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent" >  
  4.   
  5.     <com.jingchen.tbviewer.ScrollViewContainer  
  6.         android:layout_width="match_parent"  
  7.         android:layout_height="match_parent" >  
  8.   
  9.         <ScrollView  
  10.             android:layout_width="match_parent"  
  11.             android:layout_height="match_parent" >  
  12.   
  13.             <RelativeLayout  
  14.                 android:layout_width="wrap_content"  
  15.                 android:layout_height="wrap_content" >  
  16.   
  17.                 <LinearLayout  
  18.                     android:id="@+id/imagesLayout"  
  19.                     android:layout_width="match_parent"  
  20.                     android:layout_height="wrap_content"  
  21.                     android:gravity="center_horizontal"  
  22.                     android:orientation="vertical" >  
  23.   
  24.                     <ImageView  
  25.                         android:layout_width="wrap_content"  
  26.                         android:layout_height="wrap_content"  
  27.                         android:background="@drawable/h" />  
  28.   
  29.                     <ImageView  
  30.                         android:layout_width="wrap_content"  
  31.                         android:layout_height="wrap_content"  
  32.                         android:background="@drawable/i" />  
  33.   
  34.                     <ImageView  
  35.                         android:layout_width="wrap_content"  
  36.                         android:layout_height="wrap_content"  
  37.                         android:background="@drawable/j" />  
  38.   
  39.                     <ImageView  
  40.                         android:layout_width="wrap_content"  
  41.                         android:layout_height="wrap_content"  
  42.                         android:background="@drawable/k" />  
  43.   
  44.                     <ImageView  
  45.                         android:layout_width="wrap_content"  
  46.                         android:layout_height="wrap_content"  
  47.                         android:background="@drawable/l" />  
  48.   
  49.                     <ImageView  
  50.                         android:layout_width="wrap_content"  
  51.                         android:layout_height="wrap_content"  
  52.                         android:background="@drawable/m" />  
  53.                 </LinearLayout>  
  54.   
  55.                 <TextView  
  56.                     android:layout_width="match_parent"  
  57.                     android:layout_height="60dp"  
  58.                     android:layout_below="@id/imagesLayout"  
  59.                     android:background="#eeeeee"  
  60.                     android:gravity="center"  
  61.                     android:text="继续拖动,查看更多美女"  
  62.                     android:textSize="20sp" />  
  63.             </RelativeLayout>  
  64.         </ScrollView>  
  65.   
  66.         <ScrollView  
  67.             android:layout_width="match_parent"  
  68.             android:layout_height="match_parent"  
  69.             android:background="#000000" >  
  70.   
  71.             <LinearLayout  
  72.                 android:layout_width="match_parent"  
  73.                 android:layout_height="match_parent"  
  74.                 android:gravity="center_horizontal"  
  75.                 android:orientation="vertical" >  
  76.   
  77.                 <ImageView  
  78.                     android:layout_width="wrap_content"  
  79.                     android:layout_height="wrap_content"  
  80.                     android:background="@drawable/a" />  
  81.   
  82.                 <ImageView  
  83.                     android:layout_width="wrap_content"  
  84.                     android:layout_height="wrap_content"  
  85.                     android:background="@drawable/b" />  
  86.   
  87.                 <ImageView  
  88.                     android:layout_width="wrap_content"  
  89.                     android:layout_height="wrap_content"  
  90.                     android:background="@drawable/c" />  
  91.   
  92.                 <ImageView  
  93.                     android:layout_width="wrap_content"  
  94.                     android:layout_height="wrap_content"  
  95.                     android:background="@drawable/d" />  
  96.   
  97.                 <ImageView  
  98.                     android:layout_width="wrap_content"  
  99.                     android:layout_height="wrap_content"  
  100.                     android:background="@drawable/e" />  
  101.   
  102.                 <ImageView  
  103.                     android:layout_width="wrap_content"  
  104.                     android:layout_height="wrap_content"  
  105.                     android:background="@drawable/f" />  
  106.   
  107.                 <ImageView  
  108.                     android:layout_width="wrap_content"  
  109.                     android:layout_height="wrap_content"  
  110.                     android:background="@drawable/g" />  
  111.             </LinearLayout>  
  112.         </ScrollView>  
  113.     </com.jingchen.tbviewer.ScrollViewContainer>  
  114.   
  115. </RelativeLayout>  

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

 

MainActivity的代码:

 

[java] view plain copy
 
  1. package com.jingchen.tbviewer;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.Menu;  
  6.   
  7. public class MainActivity extends Activity  
  8. {  
  9.     @Override  
  10.     protected void onCreate(Bundle savedInstanceState)  
  11.     {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_main);  
  14.     }  
  15.   
  16.     @Override  
  17.     public boolean onCreateOptionsMenu(Menu menu)  
  18.     {  
  19.         getMenuInflater().inflate(R.menu.main, menu);  
  20.         return true;  
  21.     }  
  22.   
  23. }  

 

好了,到此结束~

源码下载


手机开发阅读排行

最新文章