一、常见的滑动冲突
场景1:外部滑动和内部滑动不一致
场景2:外部滑动和内部滑动一致
场景3:上面两种情况的嵌套
二、滑动冲突的处理方法
场景一:根据水平滑动还是竖直滑动判断到底由谁来拦截事件。
场景二:从业务上找突破点,比如内部为ListView,点在ListView内部的时候让ListView滑动,如果在ListView外则让父View滑动。
场景三:同样还是从业务上寻找突破点。
三、各种拦截的方法(暂时只介绍外部拦截法,内部拦截发P158)
原理:点击事件都经过父控件的拦截处理,如果父控件需要则拦截事件,不需要则不拦截。
四、示例
场景一:仿ViewPager,有三个页面,每个页面有一个ListView
使用:①、创建ScrollerLinearLayout继承LinearLayout 并在activity_main.xml中使用②、在MainActivity中动态加载三个ListView ③、重写ScrollerLinearLayout的onIntereptTouchEvent()方法
重写的逻辑:当DOWN的时候,滑动动画还在进行的时候,拦截、当MOVE的时候,判断坐标是横移的距离还是竖移距离大,如果横移大拦截,竖移大不拦截。
④、重写onTouchEvent():如果DOWN,则不处理。MOVE,根据滑动的方向和距离,根据scrollTo/scrollBy进行滑动。UP,则获取滑动的速度,根据速度判断如果速度快的话,则滑动到下一页或前一页。如果慢的话,判断如果当前位移滑动过半页了则滑动到下一页或前一页。
⑤、自动滑动的方法 详见View的滑动
步骤一:初始化相关信息
<!--已创建ScrollerLinearLayout--> <com.maikefengchao.scrollercomplict.ScrollerLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:id="@+id/main_linear_scroller" tools:context="com.maikefengchao.scrollercomplict.MainActivity"> </com.maikefengchao.scrollercomplict.ScrollerLinearLayout>
<!--需要动态载入的页面的layout--> <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/content_tv_title" android:layout_width="match_parent" android:layout_height="60dp" android:background="@android:color/holo_blue_dark"/> <ListView android:id="@+id/content_lv_list" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"></ListView> </LinearLayout>
步骤二:动态加载content_layout布局,内含ListView
public class MainActivity extends AppCompatActivity { private ScrollerLinearLayout mScrollearLinear; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView(){ //获取Horizon对象 mScrollearLinear = (ScrollerLinearLayout)findViewById(R.id.main_linear_scroller); //获取当前屏幕的宽高,为将content_layout的宽高与屏幕一直,如果是match_parent,三个布局的间距无限长。 DisplayMetrics metric = new DisplayMetrics(); getWindow().getWindowManager().getDefaultDisplay().getMetrics(metric); int screenWidth = metric.widthPixels; int screenHeight = metric.heightPixels; //将content_layout装入horizon中 :动态加载layout for(int i=0; i<3; ++i){ //加载布局获取ViewGroup ViewGroup layout = (ViewGroup) getLayoutInflater().inflate(R.layout.content_layout,mScrollearLinear,false); //设置layout的宽高 layout.getLayoutParams().width = screenWidth; layout.getLayoutParams().height = screenHeight; //动态配置View的子View TextView textView = (TextView)layout.findViewById(R.id.content_tv_title); textView.setText("Page:"+i); //设置View的背景颜色 layout.setBackgroundColor(Color.rgb(255/(i+1),255/(i+1),0)); //创建ListView createList(layout); //真正添加进View中 mScrollearLinear.addView(layout); } } //普通ListView的创建不解释 private void createList(ViewGroup viewGroup){ ListView listView = (ListView) viewGroup.findViewById(R.id.content_lv_list); ArrayList<String> arrayList = new ArrayList<>(); for(int i=0; i<30; ++i){ arrayList.add("name#"+i); } ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.listview_content_item,R.id.item_tv_content,arrayList); listView.setAdapter(adapter); } }
步骤三:重写ScrollerLinearLayout的onIntereptTouchEvent()拦截事件
private static final String TAG = "ScrollerLinearLayout"; //滑动类,用来滑动切换界面的 private Scroller mScroller; private Context mContext; //速度类,用来判断手指滑动的速度 private VelocityTracker mVelocityTracker; //onInterceptTouchEvent中,判断手指滑动的距离,选择是否拦截 private int mOldInterceptX = 0; private int mOldInterceptY = 0; //onTouchEvent中,新旧点判断移动距离,进行滑动 private int mOldX = 0; private int mOldY = 0; //当前子类的序号 private int mChildIndex = 0; //有多少个子类 private int mChildSize; //当前子类的宽度 private int mChildWidth; public ScrollerLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public ScrollerLinearLayout(Context context) { super(context); initView(context); } public ScrollerLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } //初始化对象 private void initView(Context context){ mContext = context; mScroller = new Scroller(context); mVelocityTracker = VelocityTracker.obtain(); } //获取子View的宽/高/数量信息,有四种方法获取View的宽、高、数量① @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); mChildWidth = getChildAt(0).getWidth(); mChildSize = getChildCount(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false; int currentX = (int)ev.getX(); int currentY = (int)ev.getY(); switch (ev.getAction()){ /*当子View正在滑动的时候拦截*/ case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()){ //滑动优化,可以不添加 mScroller.abortAnimation(); intercepted = true; } break; /*获取当前移动举例信息,判断用户是竖划还是横划*/ case MotionEvent.ACTION_MOVE: int deltaX = mOldInterceptX - currentX; int deltaY = mOldInterceptY - currentY; if (Math.abs(deltaX) > Math.abs(deltaY)){ intercepted = true; } else { intercepted = false; } break; /*不拦截*/ case MotionEvent.ACTION_UP: intercepted = false; break; } mOldInterceptX = currentX; mOldInterceptY = currentY; return intercepted; }
步骤四:重写onTouchEvent()方法,并配置自动滑动的类
//....接上 @Override public boolean onTouchEvent(MotionEvent event) { /*速度类需要获取事件对象,才能使用*/ mVelocityTracker.addMovement(event); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: break; /*获取滑动的距离,并用方法滑动*/ case MotionEvent.ACTION_MOVE: int destX = (int)(mOldX - event.getX()); int destY = 0; /*注:为正向左边移动,负向右边移动*/ scrollBy(destX, destY); break; /*自动滑动,当速度大于50时候或当滑动距离大于一半的时候自动滑动到下一View或者前一View*/ case MotionEvent.ACTION_UP: int scrollX = getScrollX(); //VelocityTracker的使用见其他文章 mVelocityTracker.computeCurrentVelocity(1000); float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) >= 50 ){ /*从左向右滑动为正,从右向左为负*/ mChildIndex = xVelocity>0?mChildIndex-1:mChildIndex+1; } else { //滑动超过屏幕的1/2的时候 mChildIndex = (scrollX + (mChildWidth*1)/2)/mChildWidth; } //限制只能滑动有的页面 mChildIndex = Math.max(0,Math.min(mChildIndex,mChildSize-1)); int dx = mChildWidth*mChildIndex - scrollX; //初始化滑动类的滑动信息,详见View的滑动这一章 smoothScrollBy(dx); mVelocityTracker.clear(); break; } mOldX = (int)event.getX(); mOldY = (int)event.getY(); return true; } private void smoothScrollBy(int dx){ mScroller.startScroll(getScrollX(),0,dx,0,1000); invalidate(); } @Override public void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(), 0); postInvalidate(); } }
解释①:因为这是第四章的内容,到第四章会详细解释
为什么在构造方法中,无法调用getChildAt()和getChildCount()方法。因为在构造的过程中并没有调用measure()方法,无法获取子View的任何信息。
所以同样在Activity中也一样,如果View没有完全创建完成,则Activity在任何位置都获取不到view的宽/高。
(暂时使用两种P190页详解)所以最适合调用的四种方法(第一种方法可以在View中,其他方法适用于在Activity中获取需要的View的宽高):
①、onWindowFocusChanged:该方法在View初始化完成之后才会被调用,当Activity的窗口得到焦点和失去焦点的时候都会调用一次。
@Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (hasFocus()){ int width = getMeasuredWidth(); int height = getMeasuredHeight(); } }
②、view.post(runnable):将runnable投递到消息队列的队尾。