功能描述
项目其中一项功能是检测手机屏幕好坏,此处模仿华为屏幕检测功能的实现:
在屏幕上滑动,若满屏都能感应到滑动,则完好。展示如下:
实现概述
1、隐藏activity的ActionBar和下方的虚拟按键;
2、自定义MyGridLayout继承自GridLayout,目的是为了处理触摸事件分发,重写其内的dispatchTouchEvent(MotionEvent ev)方法;
3、GridLayout自动填充TextView,因为将屏幕分割成了几十个小格,不可能一个一个在XML文件上挨个排布
实现步骤
1、创建MyGridLayout类,继承自GridLayout,覆写其分发方法。
public class MyGridLayout extends GridLayout {
/*构造函数的参数个数比较重要,可能引来Android.View.InflateException:
* Binary XML File Line 异常
* 最好将构造函数的4种重载方法都加上*/
public MyGridLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/*将所有要分发的MotionEvent的Action都改为MotionEvent.ACTION_DOWN*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
MotionEvent e = MotionEvent.obtain(ev);
e.setAction(MotionEvent.ACTION_DOWN);
return super.dispatchTouchEvent(e);
}
}
关于Android分发机制,这边文章讲得很详细,我也是看它才弄懂的。包括onTouchListener和onClickListener的区别这篇,帮助也很大。
2、创建一个EmptyActity,在其布局文件中添加一个MyGridLayout容器
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.han5472.phoneevaluation.Screen1">
<com.han5472.phoneevaluation.HelperClass.MyGridLayout
android:id="@+id/SLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnCount="5"
android:rowCount="9"
android:background="#e6e6e6">
</com.han5472.phoneevaluation.HelperClass.MyGridLayout>
</RelativeLayout>
3、Activity代码
public class Screen1 extends AppCompatActivity implements View.OnTouchListener{
private int red;
private MyGridLayout layout;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen1);
hideBottomUIMenu(); //隐藏底部虚拟按键
red = ContextCompat.getColor(this,R.color.colorAccent);//指定一种颜色
layout = (MyGridLayout) findViewById(R.id.SLayout);
/*GridLayout的自动填充*/
for(int i=0;i<9;i++)
for(int j=0;j<5;j++){
TextView textview = new TextView(this);
textview.setOnTouchListener(this); //每个textview都监听触摸事件
GridLayout.Spec rowSpec = GridLayout.spec(i,1.0f); //行坐标和比重rowweight,用float表示的
GridLayout.Spec columnSpec = GridLayout.spec(j,1.0f);//列坐标和columnweight
GridLayout.LayoutParams params = new GridLayout.LayoutParams(rowSpec,columnSpec);
layout.addView(textview,params);
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
v.setBackgroundColor(red);
return true;
case MotionEvent.ACTION_MOVE:break;
case MotionEvent.ACTION_UP: break;
}
return false;
}
protected void hideBottomUIMenu() {
//隐藏虚拟按键,并且全屏
if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api
View v = this.getWindow().getDecorView();
v.setSystemUiVisibility(View.GONE);
} else if (Build.VERSION.SDK_INT >= 19) {
//for new api versions.
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
}
}
GridLayout自动填充更详细的可以见这篇
总结
一个触摸事件的全过程:
- 触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。
- 当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。(Down事件是探路作用)
- 当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。
由于Move事件由Down事件找到的那个子View处理,所以其运动范围也局限在那个子view中,手指移动到其他子view中时也不会变红。
也即是在onTouch(View,Motion)中,view已经被Down事件确定下来,接下来的Move事件和Up事件都由它处理,这显然不满足本功能需求。
所以在其父容器ViewGroup中分发时,将所有MotionEvent的Action都变成Down,相当于拦截了后续的Move和Up事件。