android-自定义viewGroup-支持滑动

引子

自定义ViewGroup,用于实现复杂的控件特效。凡是见到的非常花哨牛逼的效果,大多可以分解为若干个 小的效果,然后通过自定义ViewGroup进行组合。但是,在组合的过程中,明明两个牛逼控件各自运行好好的,组合起来就浑身毛病,比较多见的就是滑动冲突。

今天,提供一个可横向滑动的ViewGroup,内部可以放置多个子View,而且子View可以带竖向滑动效果。

本文只提供一个基础控件,重在提供一个写控件的思路,也让我自己日后温故知新。

效果图

(每一个子view都是listView,纵向的滑动效果我没有录,相信大家都能看明白)

 

源代码

HorizontalScrollViewEx.java 这个是自定义控件的源码
  1 package tt.zhou;
  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.ViewGroup;
  8 import android.widget.Scroller;
  9 
 10 /**
 11  * 可以横向滚动的viewGroup,兼容纵向滚动的子view
 12  */
 13 public class HorizontalScrollViewEx extends ViewGroup {
 14 
 15     public HorizontalScrollViewEx(Context context) {
 16         this(context, null);
 17     }
 18 
 19     public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
 20         this(context, attrs, 0);
 21     }
 22 
 23     public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
 24         super(context, attrs, defStyleAttr);
 25         init(context);
 26     }
 27 
 28     private void init(Context context) {
 29         mScroller = new Scroller(context);
 30     }
 31 
 32     int childCount;
 33 
 34     /**
 35      * 确定每一个子view的宽高
 36      * <p>
 37      * 如果是逐个去测量子view的话,必须在测量之后,调用setMeasuredDimension来设置宽高
 38      * <p>
 39      * 这里测量出来的宽高,会在onLayout中用来作为参考
 40      *
 41      * @param widthMeasureSpec
 42      * @param heightMeasureSpec
 43      */
 44     @Override
 45     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//spec 测量模式,
 46 
 47         int width = MeasureSpec.getSize(widthMeasureSpec);
 48         int height = MeasureSpec.getSize(heightMeasureSpec);
 49         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
 50         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 51 
 52         childCount = getChildCount();
 53         measureChildren(widthMeasureSpec, heightMeasureSpec);//逐个测量所有的子view
 54 
 55         if (childCount == 0) {//如果子view数量为0,
 56             setMeasuredDimension(0, 0);//那么整个viewGroup宽高也就是0
 57         } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {//如果viewGroup的宽高都是matchParent
 58             width = childCount * getChildAt(0).getMeasuredWidth();// 那么,本viewGroup的宽,就是index为0的子view的测量宽度 乘以 子view的个数
 59             height = getChildAt(0).getMeasuredHeight();//高,就是子view的高
 60             setMeasuredDimension(width, height);//用子view的宽高,来设定
 61         } else if (widthMode == MeasureSpec.AT_MOST) {
 62             width = childCount * getChildAt(0).getMeasuredWidth();
 63             setMeasuredDimension(width, height);
 64         } else {
 65             height = getChildAt(0).getMeasuredHeight();
 66             setMeasuredDimension(width, height);
 67             Log.d("setMeasuredDimension", "" + width);
 68         }
 69     }
 70 
 71     /**
 72      * 这个方法用于,处理布局所有的子view,让他们按照代码写的规则去排布
 73      *
 74      * @param changed
 75      * @param l       left,当前viewGroup的左边线距离父组件左边线的距离
 76      * @param t       top,当前viewGroup的上边线距离父组件上边线的距离
 77      * @param r       right,当前viewGroup的左边线距离父组件右边线的距离
 78      * @param b       bottom,当前viewGroup的上边线距离父组件下边线的距离
 79      */
 80     @Override
 81     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 82         Log.d("onLayout", ":" + l + "-" + t + "-" + r + "-" + b);
 83         int count = getChildCount();
 84         int offsetX = 0;
 85         for (int i = 0; i < count; i++) {
 86             int w = getChildAt(i).getMeasuredWidth();
 87             int h = getChildAt(i).getMeasuredHeight();
 88             Log.d("onLayout", "w:" + w + " - h:" + h);
 89 
 90             getChildAt(i).layout(offsetX + l, t, offsetX + l + w, b);//保证每次都最多只完整显示一个子view,因为在onMeasure中,已经将子view的宽度设置为了 本viewGroup的宽度
 91             offsetX += w;//每次的偏移量都递增
 92         }
 93     }
 94 
 95 
 96     private float lastInterceptX, lastInterceptY;
 97 
 98     /**
 99      * 事件的拦截,
100      *
101      * @param event
102      * @return
103      */
104     @Override
105     public boolean onInterceptTouchEvent(MotionEvent event) {
106         boolean ifIntercept = false;
107         switch (event.getAction()) {
108             case MotionEvent.ACTION_DOWN:
109                 lastInterceptX = event.getRawX();
110                 lastInterceptY = event.getRawY();
111                 break;
112             case MotionEvent.ACTION_MOVE:
113                 //检查是横向移动的距离大,还是纵向
114                 float xDistance = Math.abs(lastInterceptX - event.getRawX());
115                 float yDistance = Math.abs(lastInterceptY - event.getRawY());
116                 if (xDistance > yDistance) {
117                     ifIntercept = true;
118                 } else {
119                     ifIntercept = false;
120                 }
121                 break;
122             case MotionEvent.ACTION_UP:
123                 break;
124             case MotionEvent.ACTION_CANCEL:
125                 break;
126         }
127         return ifIntercept;
128     }
129 
130     private float downX;
131     private float distanceX;
132     private boolean isFirstTouch = true;
133     private int childIndex = -1;
134 
135     @Override
136     public boolean onTouchEvent(MotionEvent event) {
137         int scrollX = getScrollX();//控件的左边界,与屏幕原点的X轴坐标
138         int scrollXMax = (getChildCount() - 1) * getChildAt(1).getMeasuredWidth();
139         final int childWidth = getChildAt(0).getWidth();
140         switch (event.getAction()) {
141             case MotionEvent.ACTION_DOWN:
142                 break;
143             case MotionEvent.ACTION_MOVE:
144                 //先让你滑动起来
145                 float moveX = event.getRawX();
146                 if (isFirstTouch) {//一次事件序列,只会赋值一次?
147                     downX = moveX;
148                     isFirstTouch = false;
149                 }
150                 Log.d("distanceX", "" + downX + "|" + moveX + "|" + distanceX);
151                 distanceX = downX - moveX;
152 
153                 //判定是否可以滑动
154                 //这里有一个隐患,由于不知道Move事件,会以什么频率来分发,所以,这里多少都会出现一点误差
155                 if (getChildCount() >= 2) {//子控件在2个或者2个以上时,才有下面的效果
156                     //如果命令是向左滑动,distanceX>0 ,那么判断命令是否可以执行
157                     //如果命令是向右滑动,distanceX<0 ,那么判断命令是否可以执行
158                     Log.d("scrollX", "scrollX:" + scrollX);
159                     if (distanceX <= 0) {
160                         if (scrollX >= 0)
161                             scrollBy((int) distanceX, 0);//滑动
162                     } else {
163                         if (scrollX <= scrollXMax)
164                             scrollBy((int) distanceX, 0);//滑动
165                     }
166                 }//如果只有一个,则不允许滑动,防止bug
167                 break;
168             case MotionEvent.ACTION_UP:// 当手指松开的时候,要显示某一个完整的子view
169 
170                 //找出view之间切换的临界点
171                 int[] edgeX = new int[getChildCount() - 1];
172                 // 计算临界点X坐标
173                 for (int i = 0; i < edgeX.length; i++) {
174                     edgeX[i] = i * childWidth + childWidth / 2;
175                     Log.d("edgeX", " - edgeX:" + edgeX[i]);
176                 }
177 
178                 childIndex = (scrollX + childWidth / 2) / childWidth;//整除的方式,来确定X轴应该所在的单元
179                 smoothScrollBy(childIndex * childWidth - scrollX, 0);// 回滚的距离
180 
181                 isFirstTouch = true;
182                 break;
183             case MotionEvent.ACTION_CANCEL:
184                 break;
185         }
186         downX = event.getRawX();
187         return super.onTouchEvent(event);
188     }
189 
190     //实现平滑地回滚
191     void smoothScrollBy(int dx, int dy) {
192         mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 500);//
193         invalidate();
194     }
195 
196     @Override
197     public void computeScroll() {
198         if (mScroller.computeScrollOffset()) {
199             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
200             invalidate();
201         }
202     }
203 
204     private Scroller mScroller;//这个scroller是为了平滑滑动
205 }

 activity_main.xml 这个是引用自定义控件的布局文件(记得改控件的包名)

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     android:layout_width="match_parent"
 5     android:layout_height="match_parent"
 6     tools:context=".MainActivity">
 7 
 8 
 9     <tt.zhou.HorizontalScrollViewEx
10         android:layout_width="match_parent"
11         android:layout_height="match_parent">
12 
13         <ListView
14             android:id="@+id/lv_1"
15             android:layout_width="match_parent"
16             android:layout_height="match_parent"
17             android:background="@android:color/holo_blue_dark"></ListView>
18 
19         <ListView
20             android:id="@+id/lv_2"
21             android:layout_width="match_parent"
22             android:layout_height="match_parent"
23             android:background="@android:color/holo_green_light"></ListView>
24 
25         <ListView
26             android:id="@+id/lv_3"
27             android:layout_width="match_parent"
28             android:layout_height="match_parent"
29             android:background="@android:color/darker_gray"></ListView>
30 
31         <ListView
32             android:id="@+id/lv_4"
33             android:layout_width="match_parent"
34             android:layout_height="match_parent"
35             android:background="@android:color/holo_blue_dark"></ListView>
36 
37         <ListView
38             android:id="@+id/lv_5"
39             android:layout_width="match_parent"
40             android:layout_height="match_parent"
41             android:background="@android:color/holo_green_light"></ListView>
42     </tt.zhou.HorizontalScrollViewEx>
43 
44 
45 </LinearLayout>

MainActivity.java  

 1 package tt.zhou;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.widget.ArrayAdapter;
 6 import android.widget.ListView;
 7 
 8 import java.util.ArrayList;
 9 import java.util.List;
10 
11 public class MainActivity extends Activity {
12 
13     ListView lv_1, lv_2, lv_3, lv_4, lv_5;
14 
15     @Override
16     protected void onCreate(Bundle savedInstanceState) {
17         super.onCreate(savedInstanceState);
18         setContentView(R.layout.activity_main);
19         initData();
20         init();
21     }
22 
23     private void init() {
24         lv_1 = findViewById(R.id.lv_1);
25         lv_2 = findViewById(R.id.lv_2);
26         lv_3 = findViewById(R.id.lv_3);
27         lv_4 = findViewById(R.id.lv_4);
28         lv_5 = findViewById(R.id.lv_5);
29 
30         ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data1);
31         lv_1.setAdapter(adapter1);
32         ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data2);
33         lv_2.setAdapter(adapter2);
34         ArrayAdapter<String> adapter3 = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data3);
35         lv_3.setAdapter(adapter3);
36         ArrayAdapter<String> adapter4 = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data4);
37         lv_4.setAdapter(adapter4);
38         ArrayAdapter<String> adapter5 = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data5);
39         lv_5.setAdapter(adapter5);
40     }
41 
42     private List<String> data1, data2, data3, data4, data5;
43 
44     private void initData() {
45         data1 = new ArrayList<>();
46         for (int i = 0; i < 100; i++) {
47             data1.add("d1-" + i);
48         }
49         data2 = new ArrayList<>();
50         for (int i = 0; i < 100; i++) {
51             data2.add("d2-" + i);
52         }
53         data3 = new ArrayList<>();
54         for (int i = 0; i < 100; i++) {
55             data3.add("d3-" + i);
56         }
57         data4 = new ArrayList<>();
58         for (int i = 0; i < 100; i++) {
59             data4.add("d4-" + i);
60         }
61         data5 = new ArrayList<>();
62         for (int i = 0; i < 100; i++) {
63             data5.add("d5-" + i);
64         }
65     }
66 }

猜你喜欢

转载自www.cnblogs.com/hankzhouAndroid/p/9130379.html