1.android布局层次
在我们的布局之上还存在四层布局, 所以我们应该减少布局的嵌套
2.事件分发
Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);能够响应这些方法的控件包括:ViewGroup 及其子类、Activity。
@1
其中:
dispatchTouchEvent(MotionEvent ev) 事件分发 Activity ViewGroup View 都有此方法
onInterceptTouchEvent(MotionEvent ev) 事件拦截 ViewGroup 有此方法
onTouchEvent(MotionEvent ev) 事件响应 Activity ViewGroup View 都有此方法
onTouch:
onTouch()是OnTouchListener接口的方法,它是获取某一个控件的触摸事件,因此使用时,必须使用setOnTouchListener绑定到控件,然后才能鉴定该控件的触摸事件。当一个View绑定了OnTouchLister后,当有touch事件触发时,就会调用onTouch方法。通过getAction()方法可以获取当前触摸事件的状态:
ACTION_DOWN:表示按下了屏幕的状态。
ACTION_MOVE :表示为移动手势
ACTION_UP :表示为离开屏幕
ACTION_CANCEL :表示取消手势,不会由用户产生,而是由程序产生的
onTouch方法的优先级高于onTouchEvent。当onTouch 方法返回true时,会进行触摸事件处理,onTouchEvent不会处理。当onTouch返回false的时候,onTouchEvent擦会处理
@2
正常情况下:activity dispatchTouchEvent -->> layout dispatchTouchEvent ->> button dispatchTouchEvent -->button onTouchevent
@--1当被onInterceptTouchEvent拦截时,会执行当前的onTouchEvent
@--2 当onTouchEvent 返回false时,返回给上一层的onTouchEvent ,如果还返回false,继续返回上一层的onTouchEvent ,直至抛出
事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
当有监听到事件时,首先由Activity的捕获到,进入事件分发处理流程。无论是Activity还是View,如前文所说,事件分发自身也具有消费能力,
如果事件分发返回true,表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。至此,事件已经完结。如果你不想Activity中的任何控件具有任何的事件消费能力,
最简答的方法可以重写此Activity的dispatchTouchEvent方法,直接返回true就ok。
如果事件分发返回 false,表明事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。
当然了,如果本层控件已经是Activity,那么事件将被系统消费或处理。
如果事件分发返回系统默认的 super.dispatchTouchEvent(ev),事件将分发给本层的事件拦截onInterceptTouchEvent 方法进行处理
(如果本层控件是Activity,由于其没有事件拦截,因此将直接将事件传递到子View,并交给子View的事件分发进行处理)。
事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理;
如果返回结果是false;则表示不对事件进行拦截,事件得以成功分发到子View。并由子View的dispatchTouchEvent进行处理。
如果返回super.onInterceptTouchEvent(ev),事件默认不会被拦截,交由子View的dispatchTouchEvent进行处理。
事件响应:public boolean onTouchEvent(MotionEvent ev)
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。
onTouchEvent 的事件响应逻辑如下:
如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
如果返回了 true 则会接收并消费该事件。
如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。
(我个人的判断是最里面的 返回false 则 其上层所有的onTouchEvent的的super 默认为false)
ScrollView 和 ListView的冲突(@1 ScrollView 嵌套Listview @2 ScrollView 嵌套一个ScrollView)
@1 ScrollView 嵌套一个ListView
原因:
@1. 因为ListView需要支持长按操作,所以ListView本身的ACTION_MOVE,会有一定的初始距离检测,小于这个距离,代表没有移动,只有从按下到移动之间坐标位置相差大 一点,才回认为ListView需要移动;
@2. ScrollView 在检测滚动事件的时候,如果ScrollView的内容的高度>ScrollView显示的高度,就认为内部的内容可以滚动,是整体滚动;ScrollView在上下滚动的时候如果 DOWN的位置与当前MOVE的位置相差很小,认为没有滚动;
@3. 当按下位置与移动的位置超过一个固定的距离之后,ScrollView再分发事件的时候,就会检测出可以滚动,那么因为ScrollView内容高度可以滚动,那么就直接滚动自身 内 容,而不把滚动事件,传递给ListView了
@4. 根据代码的分析,ScrollView判断是否开始滚动,采用的是 滚动幅度是否大于8dp的方式,ListView也是采用滚动幅度>8dp的检测;只要>8的时候,ScrollView就会先收到 事件,然后把事件就给拦截了;不再给 ListView 了;
解决方案:
@1. 重写 ListView 的 onMeasure() 让ListView计算自身所有内容的高度,全部填满ScrollView
@2. 这种方案会产生效率的问题,因为一次会把所有的Item全部显示出来;没有复用;一定要注意,通常都是显示简单的文字信息和小图片;
@3. ListView在ScrollView中,高度测量的时候,传递的模式就是 “未指定”,所以ListView默认就会计算一行的高度;
@4. 解决方案就是在ListView计算尺寸之前,强制把 未指定 调整为AT_MOST, 指定的高度可以是一个非常大的数值,这样全部显示出来;
不可取的方法(原因:需要计算出ListView的高度,全部展现出来,这就有一个隐患:不敢加图片 否则卡出翔)
原始方法,易理解:
要在listView添加完adapter后 计算高度,在之前好像貌似没用
public static void setListViewHeight(ListView listView){
if (listView == null){
return;
}
ListAdapter adapter = listView.getAdapter();
if (adapter == null) {
return;
}
int Height = 0;
/**
* 内容的长度
*/
for (int i = 0; i < adapter.getCount(); i++) {
View adapterView = adapter.getView(i, null, listView);
adapterView.measure(0,0);
Height += adapterView.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
/**
* (listView.getDividerHeight()*(adapter.getCount() - 1)
* ListView各个item的间隙长度
*/
params.height = Height + (listView.getDividerHeight()*(adapter.getCount() - 1));
listView.setLayoutParams(params);
}
简单方法,只需要重写ListView里的onMeasure方法即可
package com.treasure_ct.android_xt.seniorcontrols.eventdistribution.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.AbsListView;
import android.widget.ListView;
/**
* Created by treasure on 2016.09.27.
*/
public class MyListView extends ListView{
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int spec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, spec);
}
}
可取的方法(将可以滑动的权限调配,当手指点到LV的时候,不将滚动事件给父控件即SV 点到非LV时 吧滚动事件交给父控件SV)
重写ListView里的onInterceptTouchEvent方法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
setParentScrollAble(false);//手指触到listview的时候,让父ScrollView交出ontouch权限,也就是让父scrollview停住不能滚动
Log.d(TAG, "onInterceptTouchEvent: down");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onInterceptTouchEvent: move");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onInterceptTouchEvent: up");
break;
case MotionEvent.ACTION_CANCEL:
setParentScrollAble(true);//当手指松开时,让父ScrollView重新拿到onTouch权限
Log.d(TAG, "onInterceptTouchEvent: cancel");
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
/**
* 是否把滚动事件交给父ScrollView
*/
private void setParentScrollAble(boolean flag){
getParent().requestDisallowInterceptTouchEvent(!flag);
}
@2 ScrollView嵌套一个HorizontalScrollView 解决卡顿情况
网上的办法
package com.treasure_ct.android_xt.seniorcontrols.eventdistribution.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ScrollView;
/**
* Created by treasure on 2016.09.27.
*/
public class MyScrollView2 extends ScrollView {
private GestureDetector mGestureDetector;
View.OnTouchListener mOnTouchListener;
public MyScrollView2(Context context) {
super(context);
}
public MyScrollView2(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(new YScrollDetector());
setFadingEdgeLength(0);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
}
//如果Y角移动的绝对值大于X轴移动的绝对值,即纵向滑动返回true
class YScrollDetector extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (Math.abs(distanceY) > Math.abs(distanceX)){
return true;
}
return false;
}
}
}