功能描述

项目其中一项功能是检测手机屏幕好坏,此处模仿华为屏幕检测功能的实现:

在屏幕上滑动,若满屏都能感应到滑动,则完好。展示如下:

android 判定屏幕是否亮起 安卓手机怎么测试屏幕_Touch事件分发

实现概述

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);
        }
    }
}

总结

一个触摸事件的全过程:

  • 触摸事件由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事件。