自定义ViewGroup实现水平滑动

最近由于工作上的需求,需要实现水平滑动的功能,在网上找了一些例子没有现成的,很多人都说可以使用ViewGroup来实现

效果图

点击右边的按钮可以实现动画切换页

目录结构

关键代码实现HScrollViewGroup.Java

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.example.listviewitem;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.util.Log;  
  6. import android.view.MotionEvent;  
  7. import android.view.VelocityTracker;  
  8. import android.view.View;  
  9. import android.view.ViewConfiguration;  
  10. import android.view.ViewGroup;  
  11. import android.widget.Scroller;  
  12.   
  13. /** 
  14.  * 水平滑动或翻页,目前只支持水平滑动 2014.03.17 
  15.  *  
  16.  * @author Administrator 
  17.  *  
  18.  */  
  19. public class HScrollViewGroup extends ViewGroup {  
  20.   
  21.     private static final String TAG = "HScrollViewGroup_dzt";  
  22.     private static final int TOUCH_STATE_REST = 0;  
  23.     private static final int TOUCH_STATE_SCROLLING = 1;  
  24.     private static final int SNAP_VELOCITY = 400;// 滑动视图的速率  
  25.     private static final int INTERVAL = 4; // 每次滑动的间隔  
  26.   
  27.     private Scroller mScroller; // 滑动控件  
  28.     private VelocityTracker mVelocityTracker; // 速度追踪器  
  29.     private Direction direction = Direction.NONE;  
  30.   
  31.     private int mCurScreen; // 记录当前页  
  32.     private int mDefaultScreen = 0; // 默认页  
  33.     private int mTouchState = TOUCH_STATE_REST;// 设置触发状态  
  34.     private int mTouchSlop; // 触发移动的像素距离  
  35.     private float mLastMotionX; // 手指触碰屏幕的最后一次x坐标  
  36.     private float mLastMotionY; // 手指触碰屏幕的最后一次y坐标  
  37.     private int mTotalPage; // 总页数  
  38.     private int mMaxWidth; // 所有子控件加起来的总宽度  
  39.     private int mWidth; // 每个子控件的宽度  
  40.     private int mCtrlWidth = 0;  
  41.     private int mRemainder; // 总宽度除以每页的余数  
  42.     private int mMoveCount; // 移动计数器  
  43.     int[] mScreens = new int[5];// 每页的最前一个坐标  
  44.   
  45.     public HScrollViewGroup(Context context) {  
  46.         super(context);  
  47.         // TODO Auto-generated constructor stub  
  48.         init(context);  
  49.     }  
  50.   
  51.     public HScrollViewGroup(Context context, AttributeSet attrs) {  
  52.         this(context, attrs, 0);  
  53.         // TODO Auto-generated constructor stub  
  54.     }  
  55.   
  56.     public HScrollViewGroup(Context context, AttributeSet attrs, int defStyle) {  
  57.         super(context, attrs, defStyle);  
  58.         // TODO Auto-generated constructor stub  
  59.         init(context);  
  60.     }  
  61.   
  62.     private void init(Context context) {  
  63.         mScroller = new Scroller(context);  
  64.         mCurScreen = mDefaultScreen;// 默认设置显示第一个VIEW  
  65.         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();  
  66.     }  
  67.   
  68.     /** 
  69.      * 父类为子类在屏幕上分配实际的宽度和高度,里面的四个参数分别表示,布局是否发生改变,布局左 上右下的边距 
  70.      */  
  71.     @Override  
  72.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  73.         // TODO Auto-generated method stub  
  74.         Log.d(TAG, "onLayout changed = " + changed);  
  75.         if (changed) {  
  76.             int childLeft = 0;  
  77.             final int childCount = getChildCount();  
  78.   
  79.             Log.d(TAG, "childCount = " + childCount);  
  80.             for (int i = 0; i < childCount; i++) {  
  81.                 final View childView = getChildAt(i);  
  82.                 if (childView.getVisibility() != View.GONE) {  
  83.                     if (childCount > 5  
  84.                             && ((i != 0) && ((i % 4 == 0)) || (i == childCount - 1))) {  
  85.                         childView.layout(childLeft, 0, childLeft  
  86.                                 + (mWidth + mRemainder),  
  87.                                 childView.getMeasuredHeight());  
  88.                         childLeft += (mWidth + mRemainder);  
  89.                     } else {  
  90.                         childView.layout(childLeft, 0, childLeft + mWidth,  
  91.                                 childView.getMeasuredHeight());  
  92.                         childLeft += mWidth;  
  93.                     }  
  94.   
  95.                     Log.d(TAG, "childLeft=" + childLeft + " childWidth="  
  96.                             + mWidth);  
  97.                 }  
  98.             }  
  99.             calculateScreens();  
  100.         }  
  101.     }  
  102.   
  103.     /** 
  104.      * 计算每页第一个item的位置 
  105.      */  
  106.     void calculateScreens() {  
  107.         int childLeft = 0;  
  108.         int viewWidth = getWidth();  
  109.   
  110.         int curPage = 0;  
  111.         mScreens[curPage] = childLeft;  
  112.         ++curPage;  
  113.   
  114.         final int childCount = getChildCount();  
  115.         for (int i = 0; i < childCount; i++) {  
  116.             final View childView = getChildAt(i);  
  117.             if (childView.getVisibility() != View.GONE) {  
  118.                 if (childCount > 5  
  119.                         && ((i != 0) && ((i % 4 == 0)) || (i == childCount - 1))) {  
  120.                     childLeft += (mWidth + mRemainder);  
  121.                     if (childLeft > (viewWidth)) {  
  122.                         mScreens[curPage] = childLeft - (mWidth + mRemainder)  
  123.                                 + mScreens[curPage - 1];  
  124.                         ++curPage;  
  125.                         childLeft = (mWidth + mRemainder);  
  126.                     }  
  127.                 } else {  
  128.                     childLeft += mWidth;  
  129.                     if (childLeft > (viewWidth)) {  
  130.                         mScreens[curPage] = childLeft - mWidth  
  131.                                 + mScreens[curPage - 1];  
  132.                         ++curPage;  
  133.                         childLeft = mWidth;  
  134.                     }  
  135.                 }  
  136.   
  137.             }  
  138.             Log.d(TAG, "childLeft = " + childLeft);  
  139.         }  
  140.   
  141.         if (childLeft != 0 && curPage > 1) {  
  142.             mScreens[curPage - 1] = mScreens[curPage - 1] + childLeft  
  143.                     - viewWidth;  
  144.         }  
  145.     }  
  146.   
  147.     /** 
  148.      * MeasureSpec类的静态方法getMode和getSize来译解。一个MeasureSpec包含一个尺寸和模式。 
  149.      *  
  150.      * 有三种可能的模式: 
  151.      *  
  152.      * UNSPECIFIED:父布局没有给子布局任何限制,子布局可以任意大小。 
  153.      * EXACTLY:父布局决定子布局的确切大小。不论子布局多大,它都必须限制在这个界限里 
  154.      * 。(当布局定义为一个固定像素或者fill_parent时就是EXACTLY模式) 
  155.      * AT_MOST:子布局可以根据自己的大小选择任意大小。(当布局定义为wrap_content时就是AT_MOST模式) 
  156.      */  
  157.     @Override  
  158.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  159.         // TODO Auto-generated method stub  
  160.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  161.         final int width = MeasureSpec.getSize(widthMeasureSpec);  
  162.         final int height = MeasureSpec.getSize(heightMeasureSpec);  
  163.         if (mCtrlWidth != width) {  
  164.             mCtrlWidth = width;  
  165.             mWidth = width / 5;  
  166.             mRemainder = width % 5;  
  167.   
  168.             // The children are given the same width and height as the  
  169.             // scrollLayout  
  170.             final int count = getChildCount();  
  171.             for (int i = 0; i < count; i++) {  
  172.                 if (count > 5  
  173.                         && ((i != 0) && ((i % 4 == 0)) || (i == count - 1))) {  
  174.                     getChildAt(i).measure((mWidth + mRemainder),  
  175.                             heightMeasureSpec);  
  176.                 } else {  
  177.                     getChildAt(i).measure(mWidth, heightMeasureSpec);  
  178.                 }  
  179.             }  
  180.             mMaxWidth = (getChildCount() * mWidth) + mRemainder;  
  181.             mTotalPage = mMaxWidth / width;  
  182.             snapToScreen(mCurScreen);  
  183.             mScroller.abortAnimation();  
  184.             Log.d(TAG, "mTotalPage = " + mTotalPage + " width = " + width  
  185.                     + " height = " + height + " count = " + count  
  186.                     + " mCurScreen = " + mCurScreen);  
  187.         }  
  188.     }  
  189.   
  190.     /** 
  191.      * 根据滑动的距离判断移动到第几个视图 
  192.      */  
  193.     public void snapToDestination() {  
  194.         final int screenWidth = getWidth();  
  195.         final int scrollX = getScrollX() > mMaxWidth ? mMaxWidth : getScrollX();  
  196.         final int destScreen = (scrollX + screenWidth / 2) / screenWidth;  
  197.         Log.d(TAG, "screenWidth = " + screenWidth + " destScreen = "  
  198.                 + destScreen + " scrollx = " + scrollX);  
  199.         snapToScreen(destScreen);  
  200.     }  
  201.   
  202.     /** 
  203.      * 滚动到制定的视图 
  204.      *  
  205.      * @param whichScreen 
  206.      *            视图下标 
  207.      */  
  208.     public void snapToScreen(int whichScreen) {  
  209.         whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));  
  210.         if (getScrollX() != (whichScreen * getWidth())) {  
  211.   
  212.             // final int delta = whichScreen * getWidth() - getScrollX();  
  213.             final int delta = mScreens[mCurScreen] - getScrollX();  
  214.             Log.d(TAG, "snapToScreen-whichScreen = " + whichScreen  
  215.                     + " delta = " + delta + " scrollX = " + getScrollX());  
  216.             mScroller.startScroll(getScrollX(), 0, delta, 0, 2000);  
  217.             mCurScreen = whichScreen;  
  218.             mMoveCount = getScrollX();  
  219.             invalidate();  
  220.         }  
  221.     }  
  222.   
  223.     public void setDirection(Direction dir) {  
  224.         direction = dir;  
  225.     }  
  226.   
  227.     public int getCurScreen() {  
  228.         return mCurScreen;  
  229.     }  
  230.   
  231.     @Override  
  232.     public void computeScroll() {  
  233.         // TODO Auto-generated method stub  
  234.         if (mScroller.computeScrollOffset()) {  
  235.   
  236.             if (direction == Direction.LEFT) {  
  237.                 Log.d(TAG, "left mScreens[mCurScreen] = "  
  238.                         + mScreens[mCurScreen]);  
  239.                 mMoveCount -= INTERVAL;  
  240.                 if (mMoveCount < 0) {  
  241.                     mMoveCount = 0;  
  242.                     mScroller.abortAnimation();  
  243.                 }  
  244.                 scrollTo(mMoveCount, mScroller.getCurrY());  
  245.             } else if (direction == Direction.RIGHT) {  
  246.                 if (mScroller.getCurrX() <= mScreens[mCurScreen]) {  
  247.                     Log.d(TAG, "right mScreens[mCurScreen] = "  
  248.                             + mScreens[mCurScreen]);  
  249.                     mMoveCount += INTERVAL;  
  250.                     if (mMoveCount > mScreens[mCurScreen]) {  
  251.                         mMoveCount = mScreens[mCurScreen];  
  252.                         mScroller.abortAnimation();  
  253.                     }  
  254.                     scrollTo(mMoveCount, mScroller.getCurrY());  
  255.                 } else {  
  256.                     scrollTo(mScreens[mCurScreen], mScroller.getCurrY());  
  257.                     mScroller.abortAnimation();  
  258.                 }  
  259.             } else {  
  260.                 mScroller.forceFinished(true);  
  261.             }  
  262.             postInvalidate();  
  263.             Log.d(TAG, "computeScroll----mMoveCount = " + mMoveCount);  
  264.             Log.d(TAG, "computeScroll----x = " + mScroller.getCurrX());  
  265.         }  
  266.     }  
  267.   
  268.     @Override  
  269.     public boolean onTouchEvent(MotionEvent event) {  
  270.         // TODO Auto-generated method stub  
  271.         if (mVelocityTracker == null) {  
  272.             mVelocityTracker = VelocityTracker.obtain();  
  273.         }  
  274.         mVelocityTracker.addMovement(event);  
  275.   
  276.         final int action = event.getAction();  
  277.         final float x = event.getX();  
  278.         switch (action) {  
  279.         case MotionEvent.ACTION_DOWN:  
  280.             if (!mScroller.isFinished()) {  
  281.                 // mScroller.abortAnimation();  
  282.                 Log.d(TAG, "-----------onTouchEvent---ACTION_DOWN no finish");  
  283.                 return false;  
  284.             }  
  285.             mLastMotionX = x;  
  286.             Log.d(TAG, "down mLastMotionX = " + mLastMotionX);  
  287.             break;  
  288.   
  289.         case MotionEvent.ACTION_MOVE:  
  290.             int deltaX = (int) (mLastMotionX - x);  
  291.             mLastMotionX = x;  
  292.             Log.d(TAG, "move scroll " + getScrollX() + " mCurScreen = "  
  293.                     + mCurScreen + " mTotalPage = " + mTotalPage + " deltaX = "  
  294.                     + deltaX);  
  295.             if (getScrollX() > 0 && mCurScreen < mTotalPage)  
  296.                 scrollBy(deltaX, 0);  
  297.             break;  
  298.   
  299.         case MotionEvent.ACTION_UP:  
  300.             final VelocityTracker velocityTracker = mVelocityTracker;  
  301.             velocityTracker.computeCurrentVelocity(1000);  
  302.             int velocityX = (int) velocityTracker.getXVelocity();  
  303.   
  304.             if (velocityX > SNAP_VELOCITY && mCurScreen > 0) {  
  305.                 // 向左移动  
  306.                 Log.d(TAG, "left mCurScreen = " + mCurScreen);  
  307.                 direction = Direction.LEFT;  
  308.                 snapToScreen(mCurScreen - 1);  
  309.             } else if (velocityX < -SNAP_VELOCITY && mCurScreen < mTotalPage) {  
  310.                 // 向右移动  
  311.                 Log.d(TAG, "right mCurScreen = " + mCurScreen);  
  312.                 direction = Direction.RIGHT;  
  313.                 snapToScreen(mCurScreen + 1);  
  314.             } else {  
  315.                 direction = Direction.NONE;  
  316.                 snapToDestination();  
  317.             }  
  318.             if (mVelocityTracker != null) {  
  319.                 mVelocityTracker.recycle();  
  320.                 mVelocityTracker = null;  
  321.             }  
  322.             mTouchState = TOUCH_STATE_REST;  
  323.             break;  
  324.         case MotionEvent.ACTION_CANCEL:  
  325.             mTouchState = TOUCH_STATE_REST;  
  326.             break;  
  327.         }  
  328.         return true;  
  329.     }  
  330.   
  331.     /** 
  332.      * 用于拦截手势事件的,每个手势事件都会先调用这个方法。Layout里的onInterceptTouchEvent默认返回值是false, 
  333.      * 这样touch事件会传递到View控件 
  334.      */  
  335.     @Override  
  336.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  337.         // TODO Auto-generated method stub  
  338.         final int action = ev.getAction();  
  339.         if ((action == MotionEvent.ACTION_MOVE)  
  340.                 && (mTouchState != TOUCH_STATE_REST)) {  
  341.             return true;  
  342.         }  
  343.   
  344.         final float x = ev.getX();  
  345.         final float y = ev.getY();  
  346.   
  347.         switch (action) {  
  348.         case MotionEvent.ACTION_MOVE:  
  349.             final int xDiff = (int) Math.abs(mLastMotionX - x);  
  350.             if (xDiff > mTouchSlop) {  
  351.                 mTouchState = TOUCH_STATE_SCROLLING;  
  352.             }  
  353.             break;  
  354.   
  355.         case MotionEvent.ACTION_DOWN:  
  356.             mLastMotionX = x;  
  357.             mLastMotionY = y;  
  358.             mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST  
  359.                     : TOUCH_STATE_SCROLLING;  
  360.             break;  
  361.   
  362.         case MotionEvent.ACTION_CANCEL:  
  363.         case MotionEvent.ACTION_UP:  
  364.             mTouchState = TOUCH_STATE_REST;  
  365.             break;  
  366.         }  
  367.         return mTouchState != TOUCH_STATE_REST;  
  368.   
  369.     }  
  370.   
  371.     /** 
  372.      * 滑动的方向 
  373.      *  
  374.      * @author Administrator 
  375.      *  
  376.      */  
  377.     public enum Direction {  
  378.         LEFT, RIGHT, NONE  
  379.     }  
  380. }  

主要思路:ViewGroup在创建时会调用onMeasure回调函数计算控件的大小,调用getChildAt(i).measure()设置每个子控件的大小,设置完后会回调void onLayout(boolean changed, int l, int t, int r, int b)设置控件的布局,在ViewGroup中的子控件都会加载进来,这样就不能存放过多的控件,否则会影响性能;设置好每个控件的位置后就需要保存每页第一个子控件的位置,这样在滑动时才不会超过第一个控件和最后一个的位置。还有就是滑动时会回调computeScroll()函数来计算每将滑动的偏移位置,但有时滑动的速度不是我们需要的就需要自己去实现滑动偏移大小。

 

自定义控件在布局文件中的使用

 

[html] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:id="@+id/rela_layout"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="wrap_content"  
  5.     android:orientation="horizontal" >  
  6.   
  7.     <Button  
  8.         android:id="@+id/move"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:layout_alignParentBottom="true"  
  12.         android:layout_alignParentRight="true"  
  13.         android:background="@drawable/next_page_btn_selector" />  
  14.   
  15.     <LinearLayout  
  16.         android:layout_width="wrap_content"  
  17.         android:layout_height="wrap_content"  
  18.         android:layout_alignBottom="@id/move"  
  19.         android:layout_alignTop="@id/move"  
  20.         android:layout_toLeftOf="@id/move" >  
  21.   
  22.         <com.example.listviewitem.HScrollViewGroup  
  23.             xmlns:android="http://schemas.android.com/apk/res/android"  
  24.             android:id="@+id/hsView"  
  25.             android:layout_width="wrap_content"  
  26.             android:layout_height="wrap_content" >  
  27.   
  28.             <ImageButton  
  29.                 android:layout_width="wrap_content"  
  30.                 android:layout_height="wrap_content"  
  31.                 android:background="@drawable/scan_btn_selector"  
  32.                 android:contentDescription="@string/content_description" />  
  33.   
  34.             <ImageButton  
  35.                 android:layout_width="wrap_content"  
  36.                 android:layout_height="wrap_content"  
  37.                 android:background="@drawable/micro_sub_btn_selector"  
  38.                 android:contentDescription="@string/content_description" />  
  39.   
  40.             <ImageButton  
  41.                 android:layout_width="wrap_content"  
  42.                 android:layout_height="wrap_content"  
  43.                 android:background="@drawable/micro_add_btn_selector"  
  44.                 android:contentDescription="@string/content_description" />  
  45.   
  46.             <ImageButton  
  47.                 android:layout_width="wrap_content"  
  48.                 android:layout_height="wrap_content"  
  49.                 android:background="@drawable/previous_btn_selector"  
  50.                 android:contentDescription="@string/content_description" />  
  51.   
  52.             <ImageButton  
  53.                 android:layout_width="wrap_content"  
  54.                 android:layout_height="wrap_content"  
  55.                 android:background="@drawable/next_btn_selector"  
  56.                 android:contentDescription="@string/content_description" />  
  57.   
  58.             <ImageButton  
  59.                 android:layout_width="wrap_content"  
  60.                 android:layout_height="wrap_content"  
  61.                 android:background="@drawable/micro_add_btn_selector"  
  62.                 android:contentDescription="@string/content_description" />  
  63.   
  64.             <ImageButton  
  65.                 android:layout_width="wrap_content"  
  66.                 android:layout_height="wrap_content"  
  67.                 android:background="@drawable/previous_btn_selector"  
  68.                 android:contentDescription="@string/content_description" />  
  69.   
  70.             <ImageButton  
  71.                 android:layout_width="wrap_content"  
  72.                 android:layout_height="wrap_content"  
  73.                 android:background="@drawable/scan_btn_selector"  
  74.                 android:contentDescription="@string/content_description" />  
  75.         </com.example.listviewitem.HScrollViewGroup>  
  76.     </LinearLayout>  
  77.   
  78. </RelativeLayout>  


在Activity中进行切换

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.example.listviewitem;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.MotionEvent;  
  6. import android.view.View;  
  7. import android.view.View.OnClickListener;  
  8. import android.widget.Button;  
  9. import android.widget.RelativeLayout;  
  10.   
  11. import com.example.listviewitem.HScrollViewGroup.Direction;  
  12.   
  13. public class buttonMoveActivity extends Activity {  
  14.   
  15.     private HScrollViewGroup hsView;  
  16.     private RelativeLayout layout;  
  17.     private boolean show = true;  
  18.   
  19.     @Override  
  20.     protected void onCreate(Bundle savedInstanceState) {  
  21.         // TODO Auto-generated method stub  
  22.         super.onCreate(savedInstanceState);  
  23.         setContentView(R.layout.button_move);  
  24.         layout = (RelativeLayout) findViewById(R.id.rela_layout);  
  25.         hsView = (HScrollViewGroup) findViewById(R.id.hsView);  
  26.         final Button btn = (Button) findViewById(R.id.move);  
  27.         btn.setOnClickListener(new OnClickListener() {  
  28.   
  29.             @Override  
  30.             public void onClick(View v) {  
  31.                 // TODO Auto-generated method stub  
  32.                 if (hsView.getCurScreen() > 0) {  
  33.                     btn.setBackgroundResource(R.drawable.next_page_btn_selector);  
  34.                     hsView.setDirection(Direction.LEFT);  
  35.                     hsView.snapToScreen(0);  
  36.                 } else {  
  37.                     btn.setBackgroundResource(R.drawable.previous_page_btn_selector);  
  38.                     hsView.setDirection(Direction.RIGHT);  
  39.                     hsView.snapToScreen(1);  
  40.                 }  
  41.             }  
  42.         });  
  43.     }  
  44.   
  45.     @Override  
  46.     public boolean onTouchEvent(MotionEvent event) {  
  47.         // TODO Auto-generated method stub  
  48.         if (event.getAction() == MotionEvent.ACTION_DOWN) {  
  49.             if (show) {  
  50.                 show = false;  
  51.                 layout.setVisibility(View.GONE);  
  52.             } else {  
  53.                 show = true;  
  54.                 layout.setVisibility(View.VISIBLE);  
  55.             }  
  56.         }  
  57.         return super.onTouchEvent(event);  
  58.     }  
  59. }  

如果需要完整代码可以从以下链接下载

 

http://download.csdn.net/detail/deng0zhaotai/7055623

posted @ 2016-11-26 12:24  天涯海角路  阅读(203)  评论(0)    收藏  举报