自定义控件(视图)2期笔记12:View的滑动冲突之 外部拦截法

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

自定义控件(视图)2期笔记12:View的滑动冲突之 外部拦截法的相关文章

自定义控件(视图)2期笔记13:View的滑动冲突之 内部拦截法

1. 内部拦截法: 父容器不拦截事件,所有的事件全部都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理. 这种方法和Android中的事件分发机制不一样,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显负责一点. 我们需要重写子元素的dispatchTouchEvent方法. 这种方法的伪代码是: 1 @Override 2 public boolean dispatchTouchEvent(Motio

一个Demo带你彻底掌握View的滑动冲突

最近在重新学习Android自定义View这一块的内容,遇到了平时开发中经常碰到的一个棘手问题:View的滑动冲突.相信不少小伙伴都有相同的感觉,看似简单真正做起来却又不知道从何下手.今天就从一个简单的Demo带你彻底掌握解决View滑动冲突的办法. 老规矩,先上图: 示例图中是一个常见的下拉回弹,手指向下滑动的时候,整个布局会一起滑动.下拉到一定距离的时候松手,布局会自动回弹到开始的位置:手指向上滑动的时候,布局的子View会滑动到最底部,然后手指再向下滑动,布局的子View会滑动到最顶部,最

view的滑动冲突解决方案

一.常见的滑动冲突场景 1.外部滑动方向和内部滑动方向不一致 2.外部滑动方向和内部滑动方向一致 3.上面两种情况的嵌套 二.滑动冲突处理的原则 场景1的处理原则是:当用户左右滑动时,需要让外部的view拦截点击事件,当用户上下滑动时,需要让内部的view拦截点击事件.场景2和场景3比较特殊,无法如同场景1一样原则的处理冲突,需要在业务上寻找突破点.比如业务上规定:当处于某种状态时需要外部View响应用户的滑动,而处于另一种状态时则需要内部View来响应View的滑动,根据这种业务上的需求我们也

View的滑动冲突

View的滑动冲突指的是当有内外两层View同时可以滑动的时候,这个时候就会产生滑动冲突.那么应该如何解决滑动呢,其实要用到View的事件分发机制. View的滑动冲突主要有以下三个场景: 场景一:外部滑动方向和内部滑动方向不一致: 场景二:外部滑动方向和内部滑动方向一致: 场景三:以上两种情况的嵌套. 这里主要讨论场景一的滑动冲突的解决,其他两种思想都是类似的,根据具体情况而定. 对于场景一,它的滑动冲突处理规则是:当用户左右滑动时,需要外部的View拦截点击事件,当用户上下滑动时,需要内部的

自定义控件(视图)2期笔记11:View的滑动冲突之 概述

1. 引入: 滑动冲突可以说是日常开发中比较常见的一类问题,也是比较让人头疼的一类问题,尤其是在使用第三方框架的时候,两个原本完美的控件,组合在一起之后,忽然发现整个世界都不好了. 那到底是为什么会产生滑动冲突呢 ? 答:其实在界面中只要存在内外两层同时可以滑动,这个时候就会产生滑动冲突. 2. 常见的滑动冲突的场景: 场景1:外部滑动方向和内部滑动方向不一致 场景2:外部滑动方法和内部滑动方向一致 场景3:上面两种的嵌套 场景1:主要是将ViewPager 和Fragment配合使用所组成的页

ArcGIS API for JavaScript 4.2学习笔记[12] View的弹窗(Popup)

看本文前最好对第二章(Mapping and Views)中的Map和View类有理解. 视图类有一个属性是Popup类型的popup,查阅API知道这个就是视图的弹窗,每一个View的实例都有一个popup. 这个popup属性在View对象实例化的时候就实例化了的,即随着View的出生,它也会出生,它拥有默认的样子,它显示的文字也是默认的样式. 我们看看Popup这个类: 直接继承自Accessor,位于widgets模块下,说明Popup(弹窗)也是小部件的一种.但是为什么要单独拿出来讲呢

12.View的滑动

1.scrollTo和ScrollBy 为了实现滑动,View提供了两个方法来让我们实现这个功能,那就是scrollTo和scrollBy方法, scrollTo的方法如下: /** * Set the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @para

笔记:View Programming Guide for iOS -1

原文:View Programming Guide for iOS View and Window Architecture Views and windows present your application’s user interface and handle the interactions with that interface. UIKit and other system frameworks provide a number of views that you can use a

【IOS笔记】View Controller Basics

View Controller Basics   视图控制器基础 Apps running on iOS–based devices have a limited amount of screen space for displaying content and therefore must be creative in how they present information to the user. Apps that have lots of information to display