快乐时光

时间:2018-02-06 09:48:23来源:杰瑞文章网点击:作文字数:300字
在Android开发中,如果是一些简单的布局,都很容易搞定,但是一旦涉及到复杂的页面,特别是为了兼容小屏手机而使用了ScrollView以后,就会出现很多滑动事件的冲突,最经典的就是ScrollView中嵌套了ListView。今天主要总结一下这方面的知识点,也当作以后复习的笔记,本文主要讲述以下几点: View的事件分发机制 事件滑动冲突的思路及方法 ScrollView里面嵌套ViewPager滑动冲突问题 ViewPager里面嵌套ViewPager滑动冲突问题 Scrollview里面嵌套Listview滑动冲突问题 View的事件分发机制 关于View的事件分发机制讲解网上一搜一大堆,所以本文不细讲,而是让你理解主要的运行机制,当然也不是只是自己描述一下就结束了,会提供具体的博客参考,指引你去更详细的了解。 View的事件分发机制说白了就是点击事件的传递,也就是一个Down事件,若干个Move事件,一个Up事件构成的事件序列的传递。 下面讲述一下View事件分发机制涉及的几个方法 boolean dispatchTouchEcent(MotionEvent ev) boolean onInterceptTouchEvent(MotionEvent event) boolean onTouchEvent(MotionEvent event) public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) 前三个方法的关系用下面伪代码表示一下: public boolean dispatchTouchEvent(MotionEvent ev){ boolean consum = false; if(onInterceptTouchEvent(ev)){ consum = onTouchEvent(ev); }else{ consum = child.dispatchTouchEvent(ev); } return consum; } 根据下面这幅图逐个简单介绍下上述的四个方法: Paste_Image.png dispatchTouchEcent: 只要事件传递到了当前View,那么dispatchTouchEcent方法就一定会被调用,主要是用来分发事件的。返回结果表示是否消耗当前事件。 ture:事件就此消费,不会继续往别的地方传了,事件终止。 false:则回传给父View的onTouchEvent事件处理。 onInterceptTouchEvent: 在dispatchTouchEcent方法内部调用此方法,用来判断是否拦截某个事件。如果当前View拦截了某个事件,那么在这同一个事件序列中,此方法不会再次被调用(需要注意的是ViewGroup才有这个方法,View没有onInterceptTouchEvent这个方法)。返回结果表示是否拦截当前事件。 true:拦截事件,则交给它的 onTouchEvent 来处理。 false:不拦截该事件,传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。 onTouchEvent: 在dispatchTouchEcent方法内调用此方法,用来处理事件。返回结果表示是否处理当前事件。 true:表示消费该事件。 false:表示不处理,那么在同一个事件序列里面,当前View无法再收到后续的事件。 requestDisallowInterceptTouchEvent: 该方法中的参数disallowIntercept的意思就是childView告诉父容器要不要进行拦截。 true :告诉所有父控件不要拦截,事件交由childrenView处理; false:告诉所有父控件拦截。在父控件的onInterceptTouchEvent()中可能类似这样的处理。 这里总结一下:(结合下图看) 事件总是从上往下进行分发,即先到达Activity,再到达ViewGroup,再到达子View,如果没有任何视图消耗事件的话,事件会顺着路径往回传递。 app1.png 事件从Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的View(ViewGroup)开始一直往下(子View)传递。子View 可以通过onTouchEvent()对事件进行处理。 事件由父View(ViewGroup)传递给子View,ViewGroup 可以通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。 如果事件从上往下传递过程中一直没有被停止,且最底层子View 没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity 的onTouchEvent()函数。 如果View 没有对ACTION_DOWN 进行消费,之后的其他事件不会传递过来。 OnTouchListener 优先于onTouchEvent()对事件进行消费。 如果还不理解这几个方法的用处,请参考博文图解 Android 事件分发机制,一定要有耐心仔细看,相信看完之后你会相信事件并没有白白浪费的。 滑动冲突解决方案 滑动冲突的基本形式分为两种,其他复杂的滑动冲突都是由这两种基本形式演变而来: 外部滑动方向与内部方向不一致。 外部滑动方向与内部方向一致。 第一种可以理解为ScrollView 嵌套ViewPager,第二种可以理解为ViewPager嵌套ViewPager,稍后提供具体解决方案。 根据《Android开发艺术探索》讲述滑动冲突的拦截方法有两种: 外部拦截法 从父View着手,重写onInterceptTouchEvent方法,在父View需要拦截的时候拦截,不需要则不拦截返回false。其伪代码如下: public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int)event.getX(); int y = (int)event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; break; } case MotionEvent.ACTION_MOVE: { if (满足父容器的拦截要求) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted; } 在这里,首先down事件父容器必须返回false ,因为若是返回true,也就是拦截了down事件,那么后续的move和up事件就都会传递给父容器,子元素就没有机会处理事件了。其次是up事件也返回了false,一是因为up事件对父容器没什么意义,其次是因为若事件是子元素处理的,却没有收到up事件会让子元素的onClick事件无法触发。 内部拦截法 从子View入手,重写子元素的dispatchTouchEvent方法,父View先不要拦截任何事件,所有的 事件传递给 子View,如果子View需要此事件就消费掉,不需要此事件的话就通过requestDisallowInterceptTouchEvent方法交给父View处理。伪代码如下: @Override public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { parent.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (父容器需要此类点击事件) { parent.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); } 然后修改父容器的onInterceptTouchEvent方法: @Override public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { return false; } else { return true; } } ScrollView里面嵌套ViewPager滑动冲突问题 外部拦截法 如上面所述,从 父ViewScrollView着手,重写 OnInterceptTouchEvent方法,在上下滑动的时候拦截事件,在左右滑动的时候不拦截事件,返回 false,这样确保子View 的dispatchTouchEvent方法会被调用,代码如下: public class VerticalScrollView extends ScrollView { public VerticalScrollView(Context context) { super(context); } public VerticalScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public VerticalScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(21) public VerticalScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } private float mDownPosX = 0; private float mDownPosY = 0; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final float x = ev.getX(); final float y = ev.getY(); final int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownPosX = x; mDownPosY = y; break; case MotionEvent.ACTION_MOVE: final float deltaX = Math.abs(x - mDownPosX); final float deltaY = Math.abs(y - mDownPosY); // 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截 if (deltaX > deltaY) { return false; } } return super.onInterceptTouchEvent(ev); } } 内部拦截法 如上面上述,通过requestDisallowInterceptTouchEvent(true)方法来影响父View是否拦截事件,我们通过重写ViewPager的 dispatchTouchEvent()方法,在左右滑动的时候请求父View ScrollView不要拦截事件,其他的时候拦截事件,代码如下: public class MyViewPager extends ViewPager { private static final String TAG = "MyViewPager "; int lastX = -1; int lastY = -1; public MyViewPager(Context context) { super(context); } public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int x = (int) ev.getRawX(); int y = (int) ev.getRawY(); int dealtX = 0; int dealtY = 0; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: dealtX = 0; dealtY = 0; // 保证子View能够接收到Action_move事件 getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: dealtX += Math.abs(x - lastX); dealtY += Math.abs(y - lastY); Log.i(TAG, "dealtX:=" + dealtX); Log.i(TAG, "dealtY:=" + dealtY); // 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截 if (dealtX >= dealtY) { getParent().requestDisallowInterceptTouchEvent(true); } else { getParent().requestDisallowInterceptTouchEvent(false); } lastX = x; lastY = y; break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_UP: break; } return super.dispatchTouchEvent(ev); } } ViewPager里面嵌套ViewPager滑动冲突问题 内部拦截法: 从子View ViewPager着手,重写 子View的 dispatchTouchEvent方法,在子 View需要拦截的时候进行拦截,否则交给父View处理,代码如下: public class ChildViewPager extends ViewPager { private static final String TAG = "ChildViewPager "; public ChildViewPager(Context context) { super(context); } public ChildViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int curPosition; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: curPosition = this.getCurrentItem(); int count = this.getAdapter().getCount(); Log.i(TAG, "curPosition:=" +curPosition); // 当当前页面在最后一页和第0页的时候,由父亲拦截触摸事件 if (curPosition == count - 1|| curPosition==0) { getParent().requestDisallowInterceptTouchEvent(false); } else {//其他情况,由孩子拦截触摸事件 getParent().requestDisallowInterceptTouchEvent(true); } } return super.dispatchTouchEvent(ev); } } Scrollview里面嵌套Listview滑动冲突问题 ScrollView里面嵌套ListView,通常会出现以下两个问题: ListView的高度显示问题,常见的问题就是只显示一行; ScrollView和ListView都有上下滑动事件,放在一起会存在滑动冲突。 常用方案有如下三种: 自定义ListView public class ListViewForScroll extends ListView { public ListViewForScroll(Context context) { super(context); } public ListViewForScroll(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { intexpandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } } 主要就是重载了onMeasure方法,改变了heightMeasureSpec。这里widthMeasureSpec和heightMeasureSpec用了32位的int作为参数,高2位代表模式,有三种UNSPECIFIED、EXACTLY、AT_MOST,这是自定义View的基础知识。低30位代表数值。 MeasureSpec.makeMeasureSpec函数中第一个参数是高度的值,第二个参数是模式,makeMeasureSpec则是把模式和值合成为一个int值,这里赋给了高度。 Integer.MAX_VALUE >> 2是int类型取30位时的最大整数,即Integer.MAX_VALUE是int的最大32位值,再右移2位,就是30位,同样是最大值,只不过是30位的最大值,所以在模式上也只能选择MeasureSpec.AT_MOST。最终这个ListView的显示高度会是其能显示出来的最大值,所有的条目都会显示出来。 优点:写法简单,不影响ListView使用。 缺点: i. 由于高度设置成最大值,所有条目都会进行绘制,只是有些条目会在屏幕之外。举个例子,我传递的数据有20条,但是屏幕只够显示10条,此时用自定义的ListView会调用20次getView把所有条目都绘制出来,完全放弃了ListView的复用机制,跟直接写布局没有什么区别了,会造成页面加载速度缓慢的问题。 ii. ListView高度必须设置成match_parent。 动态测量ListView高度 public static void setListViewHeightBasedOnChildren(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { return; } int totalHeight = 0; for (int i = 0; i < listAdapter.getCount(); i++) { View listItem = listAdapter.getView(i, null, listView); listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); listView.setLayoutParams(params); } 这里就是去获取每个条目的View高度,然后所有子View高度相加得到总高度,并设置给ListView的LayoutParams。 优点:能够实现功能需求。 缺点: i. 每个条目的布局只能用LinearLayout,而不能用RelativeLayout,因为LinearLayout重写了onMeasure方法,才能调用listItem.measure(0, 0)这句,而其他布局没有。 ii. ListView高度必须设置成match_parent。 iii. 在ListView设置Adaper和调用notifyDataSetChanged时候都要调用该方法。 iv. 由于高度设置成最大值,所有条目都会进行绘制,跟第一个方法“自定义ListView”存在同样的问题。 第三是自定义LinearLayout模拟ListView。 public class LinearLayoutListView extends LinearLayout { private BaseAdapter adapter; private MyOnItemClickListener onItemClickListener; boolean footerViewAttached = false; private View footerview; public LinearLayoutListView(Context context) { super(context); initAttr(null); } public LinearLayoutListView(Context context, AttributeSet attrs) { super(context, attrs); initAttr(attrs); } public void initAttr(AttributeSet attrs) { setOrientation(VERTICAL); } /** * 初始化footerview * * @param footerView */ public void initFooterView(final View footerView) { this.footerview = footerView; } /** * 设置footerView监听事件 * * @param onClickListener */ public void setFooterViewListener(OnClickListener onClickListener) { this.footerview.setOnClickListener(onClickListener); } public BaseAdapter getAdapter() { return adapter; } /** * 设置adapter并模拟listview添加????数据 * * @param adpater */ public void setAdapter(BaseAdapter adpater) { this.adapter = adpater; removeAllViews(); if (footerViewAttached) addView(footerview); notifyChange(); } /** * 设置条目监听事件 * * @param onClickListener */ public void setOnItemClickListener(MyOnItemClickListener onClickListener) { this.onItemClickListener = onClickListener; } /** * 没有下一页了 */ public void noMorePages() { if (footerview != null && footerViewAttached) { removeView(footerview); footerViewAttached = false; } } /** * 可能还有下一?? */ public void mayHaveMorePages() { if (!footerViewAttached && footerview != null) { addView(footerview); footerViewAttached = true; } } /** * 通知更新listview */ public void notifyChange() { int count = getChildCount(); if (footerViewAttached) { count--; } LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); for (int i = count; i < adapter.getCount(); i++) { final int index = i; final LinearLayout layout = new LinearLayout(getContext()); layout.setLayoutParams(params); layout.setOrientation(VERTICAL); View v = adapter.getView(i, null, null); v.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (onItemClickListener != null) { onItemClickListener.onItemClick(LinearLayoutListView.this, layout, index, adapter.getItem(index)); } } }); ImageView imageView = new ImageView(getContext()); imageView.setBackgroundResource(R.color.background); imageView.setLayoutParams(params); layout.addView(v); layout.addView(imageView); addView(layout, index); } } public static interface MyOnItemClickListener { public void onItemClick(ViewGroup parent, View view, int position, Object o); } } 生硬的实现了ListView的基础功能,但是ListView的复用机制完全没有,跟直接写布局有何区别。 优点:能够实现功能需求。 缺点: i. ListView高度要设置成match_parent ii. 由于高度设置成最大值,所有条目都会进行绘制,跟“自定义ListView”存在同样的问题。 另外推荐解决滑动冲突方案的博文: 【Android】ListView、RecyclerView、ScrollView里嵌套ListView 相对优雅的解决方案:NestFullListView
作文投稿

快乐时光一文由杰瑞文章网免费提供,本站为公益性作文网站,此作文为网上收集或网友提供,版权归原作者所有,如果侵犯了您的权益,请及时与我们联系,我们会立即删除!

杰瑞文章网友情提示:请不要直接抄作文用来交作业。你可以学习、借鉴、期待你写出更好的作文。

快乐时光相关的作文:

说说你对这篇作文的看法吧

最新发表的关于快乐的作文

    SQL Error: select id,classid,ttid,onclick,plnum,totaldown,newspath,filename,userid,username,firsttitle,isgood,ispic,istop,isqf,ismember,isurl,truetime,lastdotime,havehtml,groupid,userfen,titlefont,titleurl,stb,fstb,restb,keyboard,eckuid,title,ftitle,newstime,titlepic,smalltext,writer,diggtop from ***_ecms_news where (classid='269') order by newstime desc limit 10

最受欢迎的关于快乐的作文

    SQL Error: select id,classid,ttid,onclick,plnum,totaldown,newspath,filename,userid,username,firsttitle,isgood,ispic,istop,isqf,ismember,isurl,truetime,lastdotime,havehtml,groupid,userfen,titlefont,titleurl,stb,fstb,restb,keyboard,eckuid,title,ftitle,newstime,titlepic,smalltext,writer,diggtop from ***_ecms_news where (classid='269') order by onclick desc limit 10