1. 外部拦截法:
点击事件通过父容器拦截处理,如果父容器需要就拦截,不需要就不拦截。
2. 下面通过一个Demo示例说明:
(1)首先我们创建一个Android工程,如下:
(2)我们来到activity_main.xml,如下:
1 <com.himi.viewconflict.ui.RevealLayout 2 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 android:orientation="vertical" 7 android:padding="12dp" 8 tools:context="${relativePackage}.${activityClass}" > 9 10 <Button 11 android:id="@+id/button1" 12 style="@style/AppTheme.Button.Green" 13 android:onClick="onButtonClick" 14 android:text="滑动冲突场景1-外部拦截" /> 15 16 </com.himi.viewconflict.ui.RevealLayout>
这里的RevealLayout是一个自定义控件(继承自ViewGroup),任何放入内部的clickable元素,当它被点击的时候,都具有波纹效果。
感觉这个RevealLayout很好用,存放自己的Github代码库之中。
(3)接下来来到MainActivity,如下:
1 package com.himi.viewconflict; 2 3 import android.app.Activity; 4 import android.content.Intent; 5 import android.os.Bundle; 6 import android.view.View; 7 8 public class MainActivity extends Activity { 9 10 @Override 11 protected void onCreate(Bundle savedInstanceState) { 12 super.onCreate(savedInstanceState); 13 setContentView(R.layout.activity_main); 14 } 15 16 17 18 public void onButtonClick(View view) { 19 Intent intent = new Intent(this, DemoActivity_1.class); 20 startActivity(intent); 21 } 22 }
(4)上面很自然地跳转到DemoActivity_1之中,如下:
package com.himi.viewconflict; import java.util.ArrayList; import com.himi.viewconflict.ui.HorizontalScrollViewEx; import com.himi.viewconflict.utils.MyUtils; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; public class DemoActivity_1 extends Activity { private static final String TAG = "DemoActivity_1"; private HorizontalScrollViewEx mListContainer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.demo_1); Log.d(TAG, "onCreate"); initView(); } private void initView() { LayoutInflater inflater = getLayoutInflater(); mListContainer = (HorizontalScrollViewEx) findViewById(R.id.container); final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels; final int screenHeight = MyUtils.getScreenMetrics(this).heightPixels; //初始化3页ListView内容 for (int i = 0; i < 3; i++) { ViewGroup layout = (ViewGroup) inflater.inflate( R.layout.content_layout, mListContainer, false); layout.getLayoutParams().width = screenWidth; TextView textView = (TextView) layout.findViewById(R.id.title); textView.setText("page " + (i + 1)); layout.setBackgroundColor(Color.rgb(255 / (i + 1), 255 / (i + 1), 0)); createList(layout); mListContainer.addView(layout); } } private void createList(ViewGroup layout) { ListView listView = (ListView) layout.findViewById(R.id.list); ArrayList<String> datas = new ArrayList<String>(); for (int i = 0; i < 50; i++) { datas.add("name " + i); } ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.content_list_item, R.id.name, datas); listView.setAdapter(adapter); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(DemoActivity_1.this, "click item "+position, Toast.LENGTH_SHORT).show(); } }); } }
上面的DemoActivity_1主布局demo_1.xml,如下:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:background="#ffffff" 6 android:orientation="vertical" > 7 8 <com.himi.viewconflict.ui.HorizontalScrollViewEx 9 android:id="@+id/container" 10 android:layout_width="wrap_content" 11 android:layout_height="match_parent" /> 12 13 14 </LinearLayout>
上面使用到HorizontalScrollViewEx是自定义控件(继承自ViewGroup),在HorizontalScrollViewEx里面实现外部拦截法逻辑,如下:
1 package com.himi.viewconflict.ui; 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.ViewGroup; 10 import android.widget.Scroller; 11 12 public class HorizontalScrollViewEx extends ViewGroup { 13 private static final String TAG = "HorizontalScrollViewEx"; 14 15 private int mChildrenSize; 16 private int mChildWidth; 17 private int mChildIndex; 18 19 // 分别记录上次滑动的坐标 20 private int mLastX = 0; 21 private int mLastY = 0; 22 // 分别记录上次滑动的坐标(onInterceptTouchEvent) 23 private int mLastXIntercept = 0; 24 private int mLastYIntercept = 0; 25 26 private Scroller mScroller; 27 private VelocityTracker mVelocityTracker; 28 29 public HorizontalScrollViewEx(Context context) { 30 super(context); 31 init(); 32 } 33 34 public HorizontalScrollViewEx(Context context, AttributeSet attrs) { 35 super(context, attrs); 36 init(); 37 } 38 39 public HorizontalScrollViewEx(Context context, AttributeSet attrs, 40 int defStyle) { 41 super(context, attrs, defStyle); 42 init(); 43 } 44 45 private void init() { 46 mScroller = new Scroller(getContext()); 47 mVelocityTracker = VelocityTracker.obtain(); 48 } 49 50 @Override 51 public boolean onInterceptTouchEvent(MotionEvent event) { 52 boolean intercepted = false; 53 int x = (int) event.getX(); 54 int y = (int) event.getY(); 55 56 switch (event.getAction()) { 57 case MotionEvent.ACTION_DOWN: { 58 intercepted = false; 59 /** 60 如果滑动动画还没结束,我们就按下了结束的按钮,那我们就结束该动画. 61 目的是为了优化滑动体验: 62 倘若用户正在水平滑动,在滑动停止之前用户迅速转化为竖直滑动,导致 63 界面在水平方向无法滑动至终点从而处于一种中间状态。为了避免这种状态, 64 用户正在水平滑动时候,下一个序列的点击事件仍然交给父容器处理,这样就不会处于中间状态 65 66 */ 67 if (!mScroller.isFinished()) { 68 mScroller.abortAnimation(); 69 intercepted = true; 70 } 71 break; 72 } 73 case MotionEvent.ACTION_MOVE: { 74 int deltaX = x - mLastXIntercept; 75 int deltaY = y - mLastYIntercept; 76 if (Math.abs(deltaX) > Math.abs(deltaY)) {//水平滑动距离差 > 竖直滑动距离差 77 intercepted = true; 78 } else {//水平滑动距离差 < 竖直滑动距离差 79 intercepted = false; 80 } 81 break; 82 } 83 case MotionEvent.ACTION_UP: { 84 intercepted = false; 85 break; 86 } 87 default: 88 break; 89 } 90 91 Log.d(TAG, "intercepted=" + intercepted); 92 mLastX = x; 93 mLastY = y; 94 mLastXIntercept = x; 95 mLastYIntercept = y; 96 97 return intercepted; 98 } 99 100 @Override 101 public boolean onTouchEvent(MotionEvent event) { 102 //表示追踪当前点击事件的速度 103 mVelocityTracker.addMovement(event); 104 int x = (int) event.getX(); 105 int y = (int) event.getY(); 106 switch (event.getAction()) { 107 case MotionEvent.ACTION_DOWN: { 108 if (!mScroller.isFinished()) { 109 mScroller.abortAnimation(); 110 } 111 break; 112 } 113 case MotionEvent.ACTION_MOVE: { 114 int deltaX = x - mLastX; 115 int deltaY = y - mLastY; 116 scrollBy(-deltaX, 0); 117 break; 118 } 119 case MotionEvent.ACTION_UP: { 120 /** 121 * 表示计算速度,比如:时间间隔为1000 ms ,在1秒内, 122 * 手指在水平方向从左向右滑过100像素,那么水平速度就是100; 123 * 计算速度+获取速度----三步曲 124 * mVelocityTracker.computeCurrentVelocity(1000); 125 * float xVelocity = mVelocityTracker.getXVelocity(); //获取水平方向的滑动速度 126 * float yVelocity = mVelocityTracker.getYVelocity();//获取垂直方向的滑动速度 127 * 由于我们需要的是xVelocity, 128 * 这里只是提一下,不计入代码; 129 * 注意:这里的速度指的是一段时间内手指所滑过的像素数!像素数!像素数!重要事说3遍; 130 */ 131 int scrollX = getScrollX(); 132 int scrollToChildIndex = scrollX / mChildWidth; 133 mVelocityTracker.computeCurrentVelocity(1000); 134 float xVelocity = mVelocityTracker.getXVelocity(); 135 136 /** 137 *当你滑动手机相册中的照片的时候有没有发现,必须滑动到一定距离它才会切到下张图片, 138 * 否则,它就回退回原来的照片了,原来,它是通过“速度”来进行控制的~ 139 * 还有就是"速度“可以为负值,很好理解,就像我们规定车前进的方向为正,反向为负; 140 * 141 */ 142 if (Math.abs(xVelocity) >= 50) { 143 mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1; 144 } else { 145 mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; 146 } 147 mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); 148 int dx = mChildIndex * mChildWidth - scrollX;//缓慢地滑动到目标的x坐标; 149 smoothScrollBy(dx, 0); 150 mVelocityTracker.clear();//对速度跟踪进行回收 151 break; 152 } 153 default: 154 break; 155 } 156 157 mLastX = x; 158 mLastY = y; 159 return true; 160 } 161 162 @Override 163 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 164 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 165 int measuredWidth = 0; 166 int measuredHeight = 0; 167 final int childCount = getChildCount(); 168 measureChildren(widthMeasureSpec, heightMeasureSpec); 169 170 int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); 171 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 172 int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); 173 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 174 if (childCount == 0) { 175 //这个方法必须由onMeasure(int, int)来调用,来存储测量的宽,高值。 176 setMeasuredDimension(0, 0); 177 178 /** 179 1.UNSPECIFIED 180 父不没有对子施加任何约束,子可以是任意大小(也就是未指定) 181 (UNSPECIFIED在源码中的处理和EXACTLY一样。当View的宽高值设置为0的时候或者没有设置宽高时, 182 模式为UNSPECIFIED) 183 2.EXACTLY 184 父决定子的确切大小,子被限定在给定的边界里,忽略本身想要的大小。 185 (当设置width或height为match_parent时,模式为EXACTLY,因为子view会占据剩余容器的空间, 186 所以它大小是确定的) 187 3.AT_MOST 188 子最大可以达到的指定大小 189 */ 190 } else if (heightSpecMode == MeasureSpec.AT_MOST) { 191 final View childView = getChildAt(0); 192 measuredHeight = childView.getMeasuredHeight(); 193 setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight()); 194 } else if (widthSpecMode == MeasureSpec.AT_MOST) { 195 final View childView = getChildAt(0); 196 measuredWidth = childView.getMeasuredWidth() * childCount; 197 setMeasuredDimension(measuredWidth, heightSpaceSize); 198 } else { 199 final View childView = getChildAt(0); 200 measuredWidth = childView.getMeasuredWidth() * childCount; 201 measuredHeight = childView.getMeasuredHeight(); 202 setMeasuredDimension(measuredWidth, measuredHeight); 203 } 204 } 205 206 @Override 207 protected void onLayout(boolean changed, int l, int t, int r, int b) { 208 int childLeft = 0; 209 final int childCount = getChildCount(); 210 mChildrenSize = childCount; 211 212 for (int i = 0; i < childCount; i++) { 213 final View childView = getChildAt(i); 214 if (childView.getVisibility() != View.GONE) { 215 final int childWidth = childView.getMeasuredWidth(); 216 mChildWidth = childWidth; 217 childView.layout(childLeft, 0, childLeft + childWidth, 218 childView.getMeasuredHeight()); 219 childLeft += childWidth; 220 } 221 } 222 } 223 224 private void smoothScrollBy(int dx, int dy) { 225 mScroller.startScroll(getScrollX(), 0, dx, 0, 500); 226 invalidate(); 227 } 228 229 /** 230 * computeScroll:主要功能是计算拖动的位移量、更新背景、 231 * 设置要显示的屏幕(setCurrentScreen(mCurrentScreen);) 232 * 通常是用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动。 233 */ 234 @Override 235 public void computeScroll() { 236 if (mScroller.computeScrollOffset()) { 237 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 238 postInvalidate(); 239 } 240 } 241 242 /** 243 * onAttachedToWindow: 是在第一次onDraw前调用的。也就是我们写的View在没有绘制出来时调用的,但只会调用一次。 244 * 比如,我们写状态栏中的时钟的View,在onAttachedToWindow这方法中做初始化工作,比如注册一些广播等等 245 */ 246 247 @Override 248 protected void onDetachedFromWindow() { 249 mVelocityTracker.recycle(); 250 super.onDetachedFromWindow(); 251 } 252 }
(5)来到主布局之中,在HorizontalScrollViewEx之中包含一个子布局content_layout.xml,如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <TextView 8 android:id="@+id/title" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:layout_marginTop="5dp" 12 android:layout_marginBottom="5dp" 13 android:text="TextView" /> 14 <!-- 15 android:cacheColorHint="#00000000":去除listview的拖动背景色 16 android:listSelector:当你不使用android:listSelector属性,默认会显示选中的item为橙黄底色, 17 有时候我们需要去掉这种效果 --> 18 <ListView 19 android:id="@+id/list" 20 android:layout_width="match_parent" 21 android:layout_height="match_parent" 22 android:background="#fff4f7f9" 23 android:cacheColorHint="#00000000" 24 android:divider="#dddbdb" 25 android:dividerHeight="1.0px" 26 android:listSelector="@android:color/transparent" /> 27 28 </LinearLayout>
这布局文件之中包含一个ListView是上下滑动,而HorizontalScrollViewEx是左右滑动的,两者之间的滑动冲突在上面使用外部拦截法解决了。
接下来就是上面Listview 的item布局,如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="50dp" 5 android:gravity="center_vertical" 6 android:orientation="vertical" > 7 8 <TextView 9 android:id="@+id/name" 10 android:layout_width="wrap_content" 11 android:layout_height="wrap_content" 12 android:text="TextView" /> 13 14 </LinearLayout>
(6)部署程序到手机上,运行效果如下:
时间: 2024-11-05 02:38:26