PointerLocationView 源码分析

package com.android.settings.macy11;

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Paint.FontMetricsInt;
import android.hardware.input.InputManager;
import android.hardware.input.InputManager.InputDeviceListener;
import android.os.SystemProperties;
import android.util.Log;
import android.util.Slog;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.view.MotionEvent.PointerCoords;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * 此类默认情况下会绘制三条曲线
 * 第一条是手指触摸屏幕的轨迹 记录down-->move-->up
 * 第二条是手指离开屏幕那个时间点(对应ACTION_UP)获取到的估计量,在经过一个算法得到前后共7个时间点的估计值绘制出的估计轨迹
 * 第三条是手指离开屏幕那个时刻通过最后那个点(对应ACTION_UP)的x,y轴的速度刻画出未来16.67ms的速度矢量直线
 * <p>
 * {@link PLView#onPointerEvent(MotionEvent)} 此方法是记录整个TouchEvent的过程
 * Exp:
 * {@link PLView#mPointers} 屏幕上一共有几条触摸轨迹(一个完整的事件流的集合)记录在这个集合里
 * 集合中的数据类型是{@link PointerState}(一个完整的事件流)
 * 几个关键的属性
 * <p>
 * 以下三个变量用于实际触摸曲线的绘制
 * {@link PointerState#mTraceX x轨迹点的坐标}
 * {@link PointerState#mTraceY y轨迹点的坐标}
 * {@link PointerState#mTraceCurrent 经过的轨迹点:false 最后的触摸点:true 两种类型的点的坐标可能会有重合}
 * <p>
 * 用以下的两个变量来绘制速度矢量直线 这两个值的获取依赖 {@link PLView#mVelocityTracker}
 * {@link PointerState#mXVelocity} x分量速度
 * {@link PointerState#mYVelocity} y分量速度
 * <p>
 * 依靠下面的这个变量来绘制估计运动曲线 这个值的获取依赖于 {@link PLView#mVelocityTracker}
 * {@link PointerState#mEstimator} 位置估计变量 基于多项式模型的指针运动估计器
 * <p>
 * {@link PLView#mVelocityTracker} 事件的速度追踪
 * <p>
 * {@link PLView#onDraw(Canvas)} 绘制
 * 其余细节代码中有详细的标注
 */
public class PLView extends View implements InputDeviceListener,
        PointerEventListener {
    private static final String TAG = "Macy11-Pointer";

    // The system property key used to specify an alternate velocity tracker strategy
    // to plot alongside the default one.  Useful for testing and comparison purposes.
    // 系统属性键,用于指定另一种速度跟踪策略,以便与默认策略一起绘图。适用于测试和比较目的。
    private static final String ALT_STRATEGY_PROPERY_KEY = "debug.velocitytracker.alt";

    //--------------------------Pointer State----------------------------//
    // 指针状态 我理解的是一条完整轨迹曲线的数据状态
    // 其中会记录一次运动轨迹曲线上从down--> move --> up 的点的信息
    public static class PointerState {
        // Trace of previous points.
        // 该数组容量会随着触摸点数的增加一直增长 只要保持在这个View内部
        private float[] mTraceX = new float[32];// x轨迹的点的坐标
        private float[] mTraceY = new float[32];// y轨迹的点的坐标
        // true:current 通过getPointerCoords 获取的点;
        // false:history 通过getHistoricalPointerCoords获取的点
        private boolean[] mTraceCurrent = new boolean[32];
        // 记录每个点的估计值
        private int mTraceCount;// 一次触摸事件的所有点的数量


        private float[] mEstimatorTraceX = new float[32];
        private float[] mEstimatorTraceY = new float[32];
        private boolean[] mEstimatorTraceCurrent = new boolean[32];
        private int mEstimatorTraceCount;


        // True if the pointer is down.
        private boolean mCurDown;

        // Most recent coordinates.
        // 坐标点的数据(这条轨迹曲线上的点的坐标信息都可以获取到)
        private PointerCoords mCoords = new PointerCoords();
        private int mToolType;// 触摸的工具

        // Most recent velocity.
        private float mXVelocity;// x轴速度
        private float mYVelocity;// y轴速度
        private float mAltXVelocity;
        private float mAltYVelocity;

        // Current bounding box, if any
        // 测试中发现这些变量一直是默认值
        private boolean mHasBoundingBox;
        private float mBoundingLeft;
        private float mBoundingTop;
        private float mBoundingRight;
        private float mBoundingBottom;

        // Position estimator. 位置估值器 通过VelocityTracker来获取
        private VelocityTracker.Estimator mEstimator = new VelocityTracker.Estimator();
        private VelocityTracker.Estimator mAltEstimator = new VelocityTracker.Estimator();

        public void clearTrace() {
            mTraceCount = 0;
            mEstimatorTraceCount = 0;
        }

        //添加一次MotionEvent事件的触摸点的轨迹 (只记录 Move & Up)
        public void addTrace(float x, float y, boolean current) {
            Log.d(TAG, " addTrace x= " + x + " y= " + y + " current= "
                    + current + " traceCount= " + mTraceCount);
            int traceCapacity = mTraceX.length;
            if (mTraceCount == traceCapacity) {//自动扩容
                traceCapacity *= 2;
                float[] newTraceX = new float[traceCapacity];
                System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount);
                mTraceX = newTraceX;

                float[] newTraceY = new float[traceCapacity];
                System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount);
                mTraceY = newTraceY;

                boolean[] newTraceCurrent = new boolean[traceCapacity];
                System.arraycopy(mTraceCurrent, 0, newTraceCurrent, 0, mTraceCount);
                mTraceCurrent = newTraceCurrent;

            }

            mTraceX[mTraceCount] = x;
            mTraceY[mTraceCount] = y;
            mTraceCurrent[mTraceCount] = current;
            mTraceCount += 1;
        }

        //添加一次MotionEvent事件的触摸点的轨迹 (只记录 Move & Up)
        public void addEstimatorTrace(float x, float y, boolean current) {
            Log.d(TAG, " addTrace Estimator x= " + x + " y= " + y + " current= "
                    + current + " traceCount= " + mEstimatorTraceCount);
            int traceCapacity = mEstimatorTraceX.length;
            if (mEstimatorTraceCount == traceCapacity) {//自动扩容
                traceCapacity *= 2;
                // estimator
                float[] newEstimatorTraceX = new float[traceCapacity];
                System.arraycopy(mEstimatorTraceX, 0, newEstimatorTraceX, 0, mEstimatorTraceCount);
                mEstimatorTraceX = newEstimatorTraceX;

                float[] newEstimatorTraceY = new float[traceCapacity];
                System.arraycopy(mEstimatorTraceY, 0, newEstimatorTraceY, 0, mEstimatorTraceCount);
                mEstimatorTraceY = newEstimatorTraceY;

                boolean[] newEstimatorCurrent = new boolean[traceCapacity];
                System.arraycopy(mEstimatorTraceCurrent, 0, newEstimatorCurrent, 0, mEstimatorTraceCount);
                mEstimatorTraceCurrent = newEstimatorCurrent;

            }
            mEstimatorTraceX[mEstimatorTraceCount] = x;
            mEstimatorTraceY[mEstimatorTraceCount] = y;
            mEstimatorTraceCurrent[mEstimatorTraceCount] = current;
            mEstimatorTraceCount += 1;
        }

        public String getTraceArray() {
            return "\nmTraceX=" + Arrays.toString(mTraceX) +
                    "\nmTraceY=" + Arrays.toString(mTraceY) +
                    "\nmTraceCurrent=" + Arrays.toString(mTraceCurrent);
        }

        public String getTraceArrayLength() {
            return "\nmTraceCount=" + mTraceCount +
                    "\nmTraceX=" + mTraceX.length +
                    "\nmTraceY=" + mTraceY.length +
                    "\nmTraceCurrent=" + mTraceCurrent.length;
        }

        public String getBounding() {
            return "\nmHasBoundingBox=" + mHasBoundingBox +
                    "\nmBoundingLeft=" + mBoundingLeft +
                    "\nmBoundingTop=" + mBoundingTop +
                    "\nmBoundingRight=" + mBoundingRight +
                    "\nmBoundingBottom=" + mBoundingBottom;
        }

        @Override
        public String toString() {
            return "PointerState{" +
                    ", mCurDown=" + mCurDown +
                    ", mCoords=" + mCoords +
                    ", mToolType=" + mToolType +
                    ", mXVelocity=" + mXVelocity +
                    ", mYVelocity=" + mYVelocity +
                    ", mAltXVelocity=" + mAltXVelocity +
                    ", mAltYVelocity=" + mAltYVelocity +
                    ", mEstimator=" + mEstimator +
                    ", mAltEstimator=" + mAltEstimator +
                    '}';
        }
    }
    //--------------------------PointerState----------------------------//

    //用来计算估计值的常量
    private final int ESTIMATE_PAST_POINTS = 4;//过去时间的4个点
    private final int ESTIMATE_FUTURE_POINTS = 2;//将来时间的2个点
    private final float ESTIMATE_INTERVAL = 0.02f;//间隔

    private final InputManager mIm;

    private final ViewConfiguration mVC;// UI中的常量
    private final Paint mTextPaint;//顶部状态栏的绘制字体
    private final Paint mTextBackgroundPaint;//状态栏的字体的背景
    private final Paint mTextLevelPaint;//
    private final Paint mPaint;
    private final Paint mCurrentPointPaint;//当前一次Move MotionEvent的最后一个点
    private final Paint mHistoryPointPaint;//当前一次Move MotionEvent的经过的所有点
    private final Paint mTargetPaint;// 十字准线
    private final Paint mPathPaint; // 触摸轨迹路径
    private final FontMetricsInt mTextMetrics = new FontMetricsInt();
    private int mHeaderBottom;
    private boolean mCurDown;
    private int mCurNumPointers;//当前触碰点的数量
    private int mMaxNumPointers;//最大触碰点的数量
    private int mActivePointerId;// 当前活跃的id of pointer
    private final ArrayList<PointerState> mPointers = new ArrayList<>();//轨迹曲线集合 子元素为一条轨迹曲线的状态数据对象
    private final PointerCoords mTempCoords = new PointerCoords();
    private final VelocityTracker.Estimator mTempEstimator = new VelocityTracker.Estimator();

    private final VelocityTracker mVelocityTracker;//速度追踪
    private final VelocityTracker mAltVelocityTracker;

    private final FasterStringBuilder mText = new FasterStringBuilder();

    private boolean mPrintCoords = true;

    public PLView(Context c) {
        super(c);
        setEnabled(true);
        setClickable(true);
        setFocusable(true);
        setFocusableInTouchMode(true);

        mIm = c.getSystemService(InputManager.class);

        mVC = ViewConfiguration.get(c);
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(10 * getResources().getDisplayMetrics().density);
        mTextPaint.setARGB(255, 0, 0, 0);

        mTextBackgroundPaint = new Paint();
        mTextBackgroundPaint.setAntiAlias(false);
        mTextBackgroundPaint.setARGB(128, 255, 255, 255);

        mTextLevelPaint = new Paint();
        mTextLevelPaint.setAntiAlias(false);
        mTextLevelPaint.setARGB(192, 255, 0, 0);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setARGB(255, 255, 255, 255);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(2);

        mCurrentPointPaint = new Paint();
        mCurrentPointPaint.setAntiAlias(true);
        mCurrentPointPaint.setARGB(255, 255, 0, 0);
        mCurrentPointPaint.setStyle(Paint.Style.STROKE);
        mCurrentPointPaint.setStrokeWidth(6);

        mHistoryPointPaint = new Paint();
        mHistoryPointPaint.setAntiAlias(true);
        mHistoryPointPaint.setARGB(255, 0, 150, 0);
        mHistoryPointPaint.setStyle(Paint.Style.STROKE);
        mHistoryPointPaint.setStrokeWidth(4);

        mTargetPaint = new Paint();
        mTargetPaint.setAntiAlias(true);
        mTargetPaint.setARGB(255, 0, 0, 192);

        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setARGB(255, 0, 0, 0);
        mPathPaint.setStrokeWidth(2);

        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(1);

        PointerState ps = new PointerState();
        Log.d(TAG, " onPointerLocation create new " + ps.toString());
        mPointers.add(ps);
        mActivePointerId = 0;

        mVelocityTracker = VelocityTracker.obtain();

        String altStrategy = SystemProperties.get(ALT_STRATEGY_PROPERY_KEY);
        Log.d(TAG, " altStrategy " + (altStrategy != null ? altStrategy + " " + altStrategy.length() : "null"));
        if (altStrategy.length() != 0) {
            Log.d(TAG, "Comparing default velocity tracker strategy with " + altStrategy);
            mAltVelocityTracker = VelocityTracker.obtain(altStrategy);
        } else {
            mAltVelocityTracker = null;
        }
    }

    public void setPrintCoords(boolean state) {
        mPrintCoords = state;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mTextPaint.getFontMetricsInt(mTextMetrics);
        mHeaderBottom = -mTextMetrics.ascent + mTextMetrics.descent + 2;
        if (true) {
            Log.i(TAG, " onPointerLocation Metrics: ascent=" + mTextMetrics.ascent
                    + " descent=" + mTextMetrics.descent
                    + " leading=" + mTextMetrics.leading
                    + " top=" + mTextMetrics.top
                    + " bottom=" + mTextMetrics.bottom);
        }
    }

    // Draw an oval.  When angle is 0 radians, orients the major axis vertically,
    // angles less than or greater than 0 radians rotate the major axis left or right.
    // 画一个椭圆。当角度为0弧度时,将主轴垂直定向,小于或大于0弧度的角将主轴向左或向右旋转。
    private RectF mReusableOvalRect = new RectF();// 可重复使用的

    //绘制椭圆 接触点的椭圆
    private void drawOval(Canvas canvas, float x, float y, float major, float minor,
                          float angle, Paint paint) {
        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        canvas.rotate((float) (angle * 180 / Math.PI), x, y);
        mReusableOvalRect.left = x - minor / 2;
        mReusableOvalRect.right = x + minor / 2;
        mReusableOvalRect.top = y - major / 2;
        mReusableOvalRect.bottom = y + major / 2;
        canvas.drawOval(mReusableOvalRect, paint);
        canvas.restore();
    }

    //从down-->move--->up(一次完整的运动轨迹曲线)
    //会向mPointers(运动轨迹曲线数据的集合)添加pointerState(一次运动轨迹曲线的数据)对象
    //从而完成对一次完整的运动轨迹曲线的描述 在对应的onDraw中绘制出轨迹
    @Override
    public void onPointerEvent(MotionEvent event) {
        Log.d(TAG, " view callback onPointerEvent " + getTagName(event));
        final int action = event.getAction();
        int pointersSize = mPointers.size();
        Log.d(TAG, " onPointerEvent NP origin pointers size " + pointersSize);
        //-----------------------Action Down----------------------------//
        if (action == MotionEvent.ACTION_DOWN
                || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
            //多点触控判断获取当前触控点索引(依次为手指触碰屏幕的点标记index,一个完整的触摸事件拥有相同的index)
            final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
                    >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down
            Log.d(TAG, " current point index " + index);
            if (action == MotionEvent.ACTION_DOWN) {// 没必要加入多指触摸的条件是因为用了for循环
                //从down事件开始 每次重置 pointers集合 中 pointerState某些状态
                for (int p = 0; p < pointersSize; p++) {// 轨迹曲线的数量
                    final PointerState ps = mPointers.get(p);
                    Log.d(TAG, " trace contain others " + ps.toString());
                    Log.d(TAG, " trace contain length " + ps.getTraceArrayLength());
                    Log.d(TAG, " trace contain bounding " + ps.getBounding());
                    Logger.longLogD(" trace contain array body ", ps.getTraceArray());
                    ps.clearTrace();
                    ps.mCurDown = false;
                }
                mCurDown = true;
                mCurNumPointers = 0;
                mMaxNumPointers = 0;
                mVelocityTracker.clear();
                if (mAltVelocityTracker != null) {
                    mAltVelocityTracker.clear();
                }
            }

            mCurNumPointers += 1;// 屏幕触摸轨迹曲线数量
            if (mMaxNumPointers < mCurNumPointers) {
                mMaxNumPointers = mCurNumPointers;
            }

            // 从屏幕上有接触点到没有接触点 系统会按照触摸的先后为每一个触摸点标记index
            // 每一个pointer 对应唯一的 index, 而index又对应唯一的pointerId
            // 可以通过这个唯一的pointerId 获取 data
            final int id = event.getPointerId(index);
            Log.d(TAG, " onPointerEvent NP ---- " + pointersSize + " getPointerId " + id);
            Log.d(TAG, " onPointerEvent mPointers.size before " + mPointers.size());
            // 大于一个触摸点的时候会进入while 循环体
            while (pointersSize <= id) {
                PointerState ps = new PointerState();
                mPointers.add(ps);
                pointersSize++;
            }
            Log.d(TAG, " onPointerEvent mPointers.size after " + mPointers.size());
            Log.d(TAG, "  id xxxx total size " + mPointers.size());
            Log.d(TAG, " onPointerEvent-->> mActivePointerId before " + mActivePointerId);
            Log.d(TAG, " onPointerEvent-->> pointerState.mCurDown " + mPointers.get(mActivePointerId).mCurDown);
            if (mActivePointerId < 0 || !mPointers.get(mActivePointerId).mCurDown) {
                mActivePointerId = id;
            }
            Log.d(TAG, " onPointerEvent mActivePointerId after " + mActivePointerId);
            final PointerState ps = mPointers.get(id);
            ps.mCurDown = true;
            InputDevice device = InputDevice.getDevice(event.getDeviceId());
            Log.d(TAG, " device xxxxx " + (device != null ? device.toString() : "null"));
            ps.mHasBoundingBox = device != null &&
                    device.getMotionRange(MotionEvent.AXIS_GENERIC_1) != null;
            Log.d(TAG, " onPointerEvent action down ps.mHasBoundingBox " + ps.mHasBoundingBox);
        }
        //-----------------------Action Down----------------------------//

        //------中间的Action Move------//
        Log.d(TAG, " onPointerEvent middle numS mCurNumPointers= " + mCurNumPointers + " mMaxNumPointers= " + mMaxNumPointers);

        //触摸点的数量
        final int pointerCountNI = event.getPointerCount();
        while (pointersSize < pointerCountNI){
            PointerState ps = new PointerState();
            mPointers.add(ps);
            pointersSize++;
        }

        Log.d(TAG, "onPointerEvent event.getPointerCount NI middle " + pointerCountNI);
        mVelocityTracker.addMovement(event);
        mVelocityTracker.computeCurrentVelocity(1);
        Log.d(TAG, "onPointerEvent mAltVelocity " + mAltVelocityTracker);
        if (mAltVelocityTracker != null) {
            mAltVelocityTracker.addMovement(event);
            mAltVelocityTracker.computeCurrentVelocity(1);
        }

        // 当次Move事件轨迹段中包含的经过点的数量 用来描述轨迹曲线的点
        final int N = event.getHistorySize();
        Log.d(TAG, "onPointerEvent----->>>>>> event.getHistorySize N " + N);
        for (int historyPos = 0; historyPos < N; historyPos++) {
            for (int i = 0; i < pointerCountNI; i++) {
                final int id = event.getPointerId(i);
                Log.d(TAG," id xxxx history ----> " + id + " size " + (mPointers.size()));
                if (id >= mPointers.size()) {
                    continue;
                }
                Log.d(TAG, "onPointerEvent Pointer history id " + id);
                final PointerState ps = mCurDown ? mPointers.get(id) : null;
                final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords;
                final VelocityTracker.Estimator estimator = ps != null ? ps.mEstimator : mTempEstimator;

                //填充历史坐标
                event.getHistoricalPointerCoords(i, historyPos, coords);
//                if (mPrintCoords) {
//                    logCoords("onPointerEvent Pointer history ", action, i, coords, id, event);
//                }
//                Logger.longLogD(TAG, "onPointerEvent historyPos & NI " + ps);
                if (ps != null) {
                    // 历史轨迹的点标记为蓝色
                    // 把历史轨迹点装箱到pointerState的数组里
                    mVelocityTracker.getEstimator(id, estimator);
                    float eX = estimator.estimateX(0);
                    float eY = estimator.estimateY(0);
                    Log.d(TAG, " xxxxxxxx history \n实际坐标: " + coords.x + " " + coords.y
                            + "\n估计坐标: " + eX + " " + eY);
                    ps.addTrace(coords.x, coords.y, false);
                    ps.addEstimatorTrace(eX, eY, false);
                }
            }
        }
        for (int i = 0; i < pointerCountNI; i++) {
            final int id = event.getPointerId(i);
            Log.d(TAG," id xxxx current ----> " + id+ " size " + (mPointers.size()));
            if (id >= mPointers.size()) {
                continue;
            }
            final PointerState ps = mCurDown ? mPointers.get(id) : null;
            final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords;
            final VelocityTracker.Estimator estimator = ps != null ? ps.mEstimator : mTempEstimator;
            //使用指定指针索引的指针坐标数据填充{@link PointerCoords}对象。
            event.getPointerCoords(i, coords);
//            if (mPrintCoords) {
//                logCoords("onPointerEvent Pointer current ", action, i, coords, id, event);
//            }
//            Logger.longLogD(TAG, "onPointerEvent only & NI " + ps);
            if (ps != null) {
                Log.d(TAG, "onPointerEvent only & NI addTrace " + coords.x + " " + coords.y + " true");
//                ps.addTrace(coords.x, coords.y, true);
                ps.mXVelocity = mVelocityTracker.getXVelocity(id);
                ps.mYVelocity = mVelocityTracker.getYVelocity(id);
                Log.d(TAG, " current pointer velocity " + ps.mXVelocity + " " + ps.mYVelocity);
                //计算估计值
                //使用指针过去的运动来预测未来的运动,获得指针运动的估计量
                //调用本地方法获取估计值0
                //之后用来绘制估计曲线
                //每次MoveEvent回调事件后 更新(实际最后绘制的是UP点的前后几个估计点构成的曲线)
                //其中up点就是对应的最后一次更新的 ps.mEstimato
                Log.d(TAG, "------------------->" + event.getAction());

                mVelocityTracker.getEstimator(id, estimator);
                float eX = estimator.estimateX(0);
                float eY = estimator.estimateY(0);
                Log.d(TAG, " xxxxxxxx current \n实际坐标: " + coords.x + " " + coords.y
                        + "\n估计坐标: " + eX + " " + eY);
                ps.addTrace(coords.x, coords.y, true);
                ps.addEstimatorTrace(eX, eY, true);
                Log.d(TAG, "onPointerEvent action middle mAltVelocity " + mAltVelocityTracker);
//                if (mAltVelocityTracker != null) {
//                    ps.mAltXVelocity = mAltVelocityTracker.getXVelocity(id);
//                    ps.mAltYVelocity = mAltVelocityTracker.getYVelocity(id);
//                    mAltVelocityTracker.getEstimator(id, ps.mAltEstimator);
//                }
//                ps.mToolType = event.getToolType(i);
//                Log.d(TAG, "onPointerEvent ps.mToolType " + ps.mToolType + " ps.mHasBoundingBox " + ps.mHasBoundingBox);
//                if (ps.mHasBoundingBox) {// it sames always false
//                    ps.mBoundingLeft = event.getAxisValue(MotionEvent.AXIS_GENERIC_1, i);
//                    ps.mBoundingTop = event.getAxisValue(MotionEvent.AXIS_GENERIC_2, i);
//                    ps.mBoundingRight = event.getAxisValue(MotionEvent.AXIS_GENERIC_3, i);
//                    ps.mBoundingBottom = event.getAxisValue(MotionEvent.AXIS_GENERIC_4, i);
//                }
            }
        }
        //-------Action Move----
        //---------------------------Action Up----------------------------//
        if (action == MotionEvent.ACTION_UP
                || action == MotionEvent.ACTION_CANCEL
                || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
            final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
                    >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP

            final int id = event.getPointerId(index);
            Log.d(TAG, "onPointerEvent action up id " + id + " NP " + pointersSize);
            if (id >= pointersSize) {
                Slog.wtf(TAG, "Got pointer ID out of bounds: id=" + id + " arraysize="
                        + pointersSize + " pointerindex=" + index
                        + " action=0x" + Integer.toHexString(action));
                return;
            }
            Log.d(TAG, "onPointerEvent action up mPointers.size " + mPointers.size());
            final PointerState ps = mPointers.get(id);
            ps.mCurDown = false;

            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                mCurDown = false;
                mCurNumPointers = 0;
            } else {
                Log.d(TAG, "onPointerEvent not action up \nmCurNumPointers " + mCurNumPointers
                        + "\nmActivePointerId " + mActivePointerId
                        + "\nid " + id);
                mCurNumPointers -= 1;
                if (mActivePointerId == id) {
                    mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
                }
                Log.d(TAG, "onPointerEvent not action up addTrace " + Float.NaN + " " + Float.NaN + " false");
                //异常状态
                ps.addTrace(Float.NaN, Float.NaN, false);
                ps.addEstimatorTrace(Float.NaN, Float.NaN, false);
            }
        }
        //---------------------------Action Up----------------------------//
        invalidate();//重新绘制
    }

    @Override
    protected void onDraw(Canvas canvas) {
        final int w = getWidth();
        final int itemW = w / 7;
        final int base = -mTextMetrics.ascent + 1;
        final int bottom = mHeaderBottom;

        final int NP = mPointers.size();
        Log.d(TAG, " onDraw NP " + NP + " mActivePointerId " + mActivePointerId);
        // Labels
        if (mActivePointerId >= 0) {
            final PointerState ps = mPointers.get(mActivePointerId);

            canvas.drawRect(0, 0, itemW - 1, bottom, mTextBackgroundPaint);
            canvas.drawText(mText.clear()
                    .append("P: ").append(mCurNumPointers)
                    .append(" / ").append(mMaxNumPointers)
                    .toString(), 1, base, mTextPaint);

            final int N = ps.mTraceCount;
            Log.d(TAG, "onDraw ps.mTraceCount " + N);
            if ((mCurDown && ps.mCurDown) || N == 0) {
                // 手指一直贴着屏幕时 会绘制手指触摸屏幕的点的坐标
                canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint);
                canvas.drawText(mText.clear()
                        .append("X: ").append(ps.mCoords.x, 1)
                        .toString(), 1 + itemW, base, mTextPaint);
                canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint);
                canvas.drawText(mText.clear()
                        .append("Y: ").append(ps.mCoords.y, 1)
                        .toString(), 1 + itemW * 2, base, mTextPaint);
            } else {
                //手指脱离屏幕后 会打印出起始到终止位置的偏移量
                float dx = ps.mTraceX[N - 1] - ps.mTraceX[0];
                float dy = ps.mTraceY[N - 1] - ps.mTraceY[0];
                canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom,
                        Math.abs(dx) < mVC.getScaledTouchSlop()
                                ? mTextBackgroundPaint : mTextLevelPaint);
                canvas.drawText(mText.clear()
                        .append("dX: ").append(dx, 1)
                        .toString(), 1 + itemW, base, mTextPaint);
                canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom,
                        Math.abs(dy) < mVC.getScaledTouchSlop()
                                ? mTextBackgroundPaint : mTextLevelPaint);
                canvas.drawText(mText.clear()
                        .append("dY: ").append(dy, 1)
                        .toString(), 1 + itemW * 2, base, mTextPaint);
            }

            canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint);
            canvas.drawText(mText.clear()
                    .append("Xv: ").append(ps.mXVelocity, 3)
                    .toString(), 1 + itemW * 3, base, mTextPaint);

            canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint);
            canvas.drawText(mText.clear()
                    .append("Yv: ").append(ps.mYVelocity, 3)
                    .toString(), 1 + itemW * 4, base, mTextPaint);

            canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint);
            canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCoords.pressure * itemW) - 1,
                    bottom, mTextLevelPaint);
            canvas.drawText(mText.clear()
                    .append("Prs: ").append(ps.mCoords.pressure, 2)
                    .toString(), 1 + itemW * 5, base, mTextPaint);

            canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint);
            canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCoords.size * itemW) - 1,
                    bottom, mTextLevelPaint);
            canvas.drawText(mText.clear()
                    .append("Size: ").append(ps.mCoords.size, 2)
                    .toString(), 1 + itemW * 6, base, mTextPaint);
        }

        Log.d(TAG, "onDraw draw pointer trace NP " + NP);
        // Pointer trace.
        for (int p = 0; p < NP; p++) {
            final PointerState ps = mPointers.get(p);

            // Draw path.
            // 每次都会从最开始的那个点开始绘制到当前手指移动到的位置 一直到手指离开屏幕
            final int traceCount = ps.mTraceCount;
            final int estimatorTraceCount = ps.mEstimatorTraceCount;
            Log.d(TAG, " onDraw path ps.mTraceCount " + traceCount);
            float lastX = 0, lastY = 0;
            boolean haveLast = false;
            boolean drawn = false;
            mPaint.setARGB(255, 128, 255, 255);
            for (int i = 0; i < traceCount; i++) {
                float x = ps.mTraceX[i];
                float y = ps.mTraceY[i];
                Log.d(TAG, " ----->>>>>> " + x + " " + y);
                Log.d(TAG, "onDraw -- isNaN " + i + " isNaN " + (Float.isNaN(x)));
                if (Float.isNaN(x)) {
                    haveLast = false;
                    continue;
                }
                Log.d(TAG, "onDraw -- haveLast " + i + " haveLast " + haveLast);
                if (haveLast) {
                    //画出运动轨迹线
                    canvas.drawLine(lastX, lastY, x, y, mPathPaint);
                    //画出运动轨迹的点
                    final Paint paint = ps.mTraceCurrent[i] ? mCurrentPointPaint : mHistoryPointPaint;
                    canvas.drawPoint(lastX, lastY, paint);
                    drawn = true;
                    Log.d(TAG, " onDraw path ps.mTraceCount v " + i);
                    //绘制 1 ------ 最后一个点
                }
                lastX = x;
                lastY = y;
                haveLast = true;
            }
            Log.d(TAG, "onDraw drawn " + drawn);
            if (drawn) {
                float lx = 0, ly = 0;
                boolean haveLast_Estimater = false;
                // Draw movement estimate curve.
//                mPaint.setARGB(128, 128, 0, 128);
                mPaint.setARGB(255, 255, 95, 33);
//                mCurrentPointPaint.setARGB(255,);
//                mHistoryPointPaint.setARGB(255,);
                // 橙色的运动轨迹估计曲线
                // 绘制了手指离开屏幕前后几个点得到估计运动曲线
                // 每段OnMove回调后计算出的运动估计值
//                float lx = ps.mEstimator.estimateX(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
//                float ly = ps.mEstimator.estimateY(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
                //起始绘制time点是 -0.08f
                //进行[-3,2] 共6段直线的绘制
                // (-0.08,-0.06)(-0.06,-0.04)(-0.04,-0.02)(-0.02,0)(0,0.02)(0.02,0.04) 这些区间点都是时刻点
                // 0时刻就是对应的 一组事件最后的Up点记录
//                for (int i = -ESTIMATE_PAST_POINTS + 1; i <= ESTIMATE_FUTURE_POINTS; i++) {
//                    float x = ps.mEstimator.estimateX(i * ESTIMATE_INTERVAL);
//                    float y = ps.mEstimator.estimateY(i * ESTIMATE_INTERVAL);
//                    canvas.drawLine(lx, ly, x, y, mPaint);
//                    Log.d(TAG, " 轨迹估计曲线 " + " lx= " + lx + " ly=" + ly + " x= " + x + "y= " + y);
//                    lx = x;
//                    ly = y;
//                }
                for (int i = 0; i < estimatorTraceCount; i++) {
                    float x = ps.mEstimatorTraceX[i];
                    float y = ps.mEstimatorTraceY[i];
                    if (Float.isNaN(x)) {
                        haveLast_Estimater = false;
                        continue;
                    }
                    if (haveLast_Estimater) {
                        //画出运动轨迹线
                        canvas.drawLine(lx, ly, x, y, mPaint);
                        Log.d(TAG, " estimator== ly " + ly + " lx " + lx + " x " + x + " y " + y);
                        //画出运动轨迹的点
                        final Paint paint = ps.mTraceCurrent[i] ? mCurrentPointPaint : mHistoryPointPaint;
//                        canvas.drawPoint(lastX, lastY, paint);
                        //绘制 1 ------ 最后一个点
                    }
                    lx = x;
                    ly = y;
                    haveLast_Estimater = true;
                }

                // Draw velocity vector.
//                mPaint.setARGB(255, 255, 64, 128);
                mPaint.setARGB(255, 0, 0, 255);
                //蓝色的速度矢量直线
                float xVel = ps.mXVelocity * (1000 / 60); // pixel/ms * 1000 / 60 --> 未来16.67ms 经过的距离
                float yVel = ps.mYVelocity * (1000 / 60);
                canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);

                Log.d(TAG, "xxxxxx xVel " + xVel + " yVel " + yVel);

//                // Draw alternate estimate. 目前没用 mAltVelocity null
//                Log.d(TAG,"onDraw Draw alternate estimate mAltVelocity " + mAltVelocity);
//                if (mAltVelocity != null) {
//                    mPaint.setARGB(128, 0, 128, 128);
//                    lx = ps.mAltEstimator.estimateX(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
//                    ly = ps.mAltEstimator.estimateY(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
//                    for (int i = -ESTIMATE_PAST_POINTS + 1; i <= ESTIMATE_FUTURE_POINTS; i++) {
//                        float x = ps.mAltEstimator.estimateX(i * ESTIMATE_INTERVAL);
//                        float y = ps.mAltEstimator.estimateY(i * ESTIMATE_INTERVAL);
//                        canvas.drawLine(lx, ly, x, y, mPaint);
//                        lx = x;
//                        ly = y;
//                    }
//
//                    mPaint.setARGB(255, 64, 255, 128);
//                    xVel = ps.mAltXVelocity * (1000 / 60);
//                    yVel = ps.mAltYVelocity * (1000 / 60);
//                    canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
//                }
            }
//            Log.d(TAG, "onDraw mCurDown " + (mCurDown && ps.mCurDown));
//            if (mCurDown && ps.mCurDown) {
//                //绘制十字准线 x y 轴
//                // Draw crosshairs.
//                //x轴
                canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint);
//                //y轴
                canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint);
//
//                // Draw current point. 十字轴准星点
                int pressureLevel = (int) (ps.mCoords.pressure * 255);
                mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
                canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
//
//                // Draw current touch ellipse.
                mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
                drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
                        ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
//
//                // Draw current tool ellipse.
                mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
                drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
                        ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
//
//                // Draw the orientation arrow.
                float arrowSize = ps.mCoords.toolMajor * 0.7f;
                if (arrowSize < 20) {
                    arrowSize = 20;
                }
                mPaint.setARGB(255, pressureLevel, 255, 0);
                float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
                        * arrowSize);
                float orientationVectorY = (float) (-Math.cos(ps.mCoords.orientation)
                        * arrowSize);
                if (ps.mToolType == MotionEvent.TOOL_TYPE_STYLUS
                        || ps.mToolType == MotionEvent.TOOL_TYPE_ERASER) {
                    // Show full circle orientation.
                    canvas.drawLine(ps.mCoords.x, ps.mCoords.y,
                            ps.mCoords.x + orientationVectorX,
                            ps.mCoords.y + orientationVectorY,
                            mPaint);
                } else {
                    // Show half circle orientation.
                    canvas.drawLine(
                            ps.mCoords.x - orientationVectorX,
                            ps.mCoords.y - orientationVectorY,
                            ps.mCoords.x + orientationVectorX,
                            ps.mCoords.y + orientationVectorY,
                            mPaint);
                }
//
//                // Draw the tilt point along the orientation arrow.
                float tiltScale = (float) Math.sin(
                        ps.mCoords.getAxisValue(MotionEvent.AXIS_TILT));
                canvas.drawCircle(
                        ps.mCoords.x + orientationVectorX * tiltScale,
                        ps.mCoords.y + orientationVectorY * tiltScale,
                        3.0f, mPaint);
//
//                // Draw the current bounding box
                if (ps.mHasBoundingBox) {
                    canvas.drawRect(ps.mBoundingLeft, ps.mBoundingTop,
                            ps.mBoundingRight, ps.mBoundingBottom, mPaint);
                }
//            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onPointerEvent----->>>>>> view callback onTouchEvent " + event.getAction());
//        printSamples(event);
        onPointerEvent(event);
        if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) {
            requestFocus();
        }
        return true;
    }

    private void printSamples(MotionEvent ev) {
        final int historySize = ev.getHistorySize();
        final int pointerCount = ev.getPointerCount();
        for (int h = 0; h < historySize; h++) {
            Log.d("macy777", "At time historyET " + ev.getHistoricalEventTime(h));
            for (int p = 0; p < pointerCount; p++) {
                Log.d("macy777", "  pointerId " +
                        ev.getPointerId(p) + " historyX "
                        + ev.getHistoricalX(p, h)
                        + " historyY " + ev.getHistoricalY(p, h));
            }
        }
        Log.d("macy777", "At time ET " + ev.getEventTime());
        for (int p = 0; p < pointerCount; p++) {
            Log.d("macy777", "  pointerId " +
                    ev.getPointerId(p) + " X " + ev.getX(p) + " Y " + ev.getY(p));
        }
    }

    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {
        Log.d(TAG, " view callback onGenericMotionEvent ");
        final int source = event.getSource();
        if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
            onPointerEvent(event);
        } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
            logMotionEvent("Joystick", event);
        } else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) {
            logMotionEvent("Position", event);
        } else {
            logMotionEvent("Generic", event);
        }
        return true;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (shouldLogKey(keyCode)) {
            final int repeatCount = event.getRepeatCount();
            if (repeatCount == 0) {
                Log.i(TAG, "Key Down: " + event);
            } else {
                Log.i(TAG, "Key Repeat #" + repeatCount + ": " + event);
            }
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (shouldLogKey(keyCode)) {
            Log.i(TAG, "Key Up: " + event);
            return true;
        }
        return super.onKeyUp(keyCode, event);
    }

    private static boolean shouldLogKey(int keyCode) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_UP:
            case KeyEvent.KEYCODE_DPAD_DOWN:
            case KeyEvent.KEYCODE_DPAD_LEFT:
            case KeyEvent.KEYCODE_DPAD_RIGHT:
            case KeyEvent.KEYCODE_DPAD_CENTER:
                return true;
            default:
                return KeyEvent.isGamepadButton(keyCode)
                        || KeyEvent.isModifierKey(keyCode);
        }
    }

    @Override
    public boolean onTrackballEvent(MotionEvent event) {
        Log.d(TAG, " view callback onTrackballEvent ");
        logMotionEvent("Trackball", event);
        return true;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        mIm.registerInputDeviceListener(this, getHandler());
        logInputDevices();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        mIm.unregisterInputDeviceListener(this);
    }

    @Override
    public void onInputDeviceAdded(int deviceId) {
        logInputDeviceState(deviceId, "Device Added");
    }

    @Override
    public void onInputDeviceChanged(int deviceId) {
        logInputDeviceState(deviceId, "Device Changed");
    }

    @Override
    public void onInputDeviceRemoved(int deviceId) {
        logInputDeviceState(deviceId, "Device Removed");
    }

    private void logInputDevices() {
        int[] deviceIds = InputDevice.getDeviceIds();
        for (int i = 0; i < deviceIds.length; i++) {
            logInputDeviceState(deviceIds[i], "Device Enumerated");
        }
    }

    private void logInputDeviceState(int deviceId, String state) {
        InputDevice device = mIm.getInputDevice(deviceId);
        if (device != null) {
            Log.i(TAG, state + ": " + device);
        } else {
            Log.i(TAG, state + ": " + deviceId);
        }
    }

    private void logMotionEvent(String type, MotionEvent event) {
        final int action = event.getAction();
        final int N = event.getHistorySize();
        final int NI = event.getPointerCount();
        for (int historyPos = 0; historyPos < N; historyPos++) {
            for (int i = 0; i < NI; i++) {
                final int id = event.getPointerId(i);
                event.getHistoricalPointerCoords(i, historyPos, mTempCoords);
                logCoords(type, action, i, mTempCoords, id, event);
            }
        }
        for (int i = 0; i < NI; i++) {
            final int id = event.getPointerId(i);
            event.getPointerCoords(i, mTempCoords);
            logCoords(type, action, i, mTempCoords, id, event);
        }
    }

    private void logCoords(String type, int action, int index,
                           MotionEvent.PointerCoords coords, int id, MotionEvent event) {
        final int toolType = event.getToolType(index);
        final int buttonState = event.getButtonState();
        final String prefix;
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                prefix = "DOWN";
                break;
            case MotionEvent.ACTION_UP:
                prefix = "UP";
                break;
            case MotionEvent.ACTION_MOVE:
                prefix = "MOVE";
                break;
            case MotionEvent.ACTION_CANCEL:
                prefix = "CANCEL";
                break;
            case MotionEvent.ACTION_OUTSIDE:
                prefix = "OUTSIDE";
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) {
                    prefix = "DOWN";
                } else {
                    prefix = "MOVE";
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) {
                    prefix = "UP";
                } else {
                    prefix = "MOVE";
                }
                break;
            case MotionEvent.ACTION_HOVER_MOVE:
                prefix = "HOVER MOVE";
                break;
            case MotionEvent.ACTION_HOVER_ENTER:
                prefix = "HOVER ENTER";
                break;
            case MotionEvent.ACTION_HOVER_EXIT:
                prefix = "HOVER EXIT";
                break;
            case MotionEvent.ACTION_SCROLL:
                prefix = "SCROLL";
                break;
            default:
                prefix = Integer.toString(action);
                break;
        }

        Log.i(TAG, mText.clear()
                .append(type).append(" id ").append(id + 1)
                .append(": ")
                .append(prefix)
                .append(" (").append(coords.x, 3).append(", ").append(coords.y, 3)
                .append(") Pressure=").append(coords.pressure, 3)
                .append(" Size=").append(coords.size, 3)
                .append(" TouchMajor=").append(coords.touchMajor, 3)
                .append(" TouchMinor=").append(coords.touchMinor, 3)
                .append(" ToolMajor=").append(coords.toolMajor, 3)
                .append(" ToolMinor=").append(coords.toolMinor, 3)
                .append(" Orientation=").append((float) (coords.orientation * 180 / Math.PI), 1)
                .append("deg")
                .append(" Tilt=").append((float) (
                        coords.getAxisValue(MotionEvent.AXIS_TILT) * 180 / Math.PI), 1)
                .append("deg")
                .append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1)
                .append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1)
                .append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1)
                .append(" BoundingBox=[(")
                .append(event.getAxisValue(MotionEvent.AXIS_GENERIC_1), 3)
                .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_2), 3).append(")")
                .append(", (").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_3), 3)
                .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_4), 3)
                .append(")]")
                .append(" ToolType=").append(MotionEvent.toolTypeToString(toolType))
                .append(" ButtonState=").append(MotionEvent.buttonStateToString(buttonState))
                .toString());
    }

    //fdfdfsd
    private String getTagName(MotionEvent motionEvent) {
        int action = motionEvent.getAction();
        String prefix = "default";
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                prefix = "DOWN";
                break;
            case MotionEvent.ACTION_UP:
                prefix = "UP";
                break;
            case MotionEvent.ACTION_MOVE:
                prefix = "MOVE";
                break;
            case MotionEvent.ACTION_CANCEL:
                prefix = "CANCEL";
                break;
            case MotionEvent.ACTION_OUTSIDE:
                prefix = "OUTSIDE";
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                prefix = "ACTION_POINTER_DOWN";
                break;
            case MotionEvent.ACTION_POINTER_UP:
                prefix = "ACTION_POINTER_UP";
                break;
            case MotionEvent.ACTION_HOVER_MOVE:
                prefix = "HOVER MOVE";
                break;
            case MotionEvent.ACTION_HOVER_ENTER:
                prefix = "HOVER ENTER";
                break;
            case MotionEvent.ACTION_HOVER_EXIT:
                prefix = "HOVER EXIT";
                break;
            case MotionEvent.ACTION_SCROLL:
                prefix = "SCROLL";
                break;
            default:
                prefix = Integer.toString(action);
                break;
        }
        return prefix;
    }

    // HACK
    // A quick and dirty string builder implementation optimized for GC.
    // Using String.format causes the application grind to a halt when
    // more than a couple of pointers are down due to the number of
    // temporary objects allocated while formatting strings for drawing or logging.
    private static final class FasterStringBuilder {
        private char[] mChars;
        private int mLength;

        public FasterStringBuilder() {
            mChars = new char[64];
        }

        public FasterStringBuilder clear() {
            mLength = 0;
            return this;
        }

        public FasterStringBuilder append(String value) {
            final int valueLength = value.length();
            final int index = reserve(valueLength);
            value.getChars(0, valueLength, mChars, index);
            mLength += valueLength;
            return this;
        }

        public FasterStringBuilder append(int value) {
            return append(value, 0);
        }

        public FasterStringBuilder append(int value, int zeroPadWidth) {
            final boolean negative = value < 0;
            if (negative) {
                value = -value;
                if (value < 0) {
                    append("-2147483648");
                    return this;
                }
            }

            int index = reserve(11);
            final char[] chars = mChars;

            if (value == 0) {
                chars[index++] = '0';
                mLength += 1;
                return this;
            }

            if (negative) {
                chars[index++] = '-';
            }

            int divisor = 1000000000;
            int numberWidth = 10;
            while (value < divisor) {
                divisor /= 10;
                numberWidth -= 1;
                if (numberWidth < zeroPadWidth) {
                    chars[index++] = '0';
                }
            }

            do {
                int digit = value / divisor;
                value -= digit * divisor;
                divisor /= 10;
                chars[index++] = (char) (digit + '0');
            } while (divisor != 0);

            mLength = index;
            return this;
        }

        public FasterStringBuilder append(float value, int precision) {
            int scale = 1;
            for (int i = 0; i < precision; i++) {
                scale *= 10;
            }
            value = (float) (Math.rint(value * scale) / scale);

            append((int) value);

            if (precision != 0) {
                append(".");
                value = Math.abs(value);
                value -= Math.floor(value);
                append((int) (value * scale), precision);
            }

            return this;
        }

        @Override
        public String toString() {
            return new String(mChars, 0, mLength);
        }

        private int reserve(int length) {
            final int oldLength = mLength;
            final int newLength = mLength + length;
            final char[] oldChars = mChars;
            final int oldCapacity = oldChars.length;
            if (newLength > oldCapacity) {
                final int newCapacity = oldCapacity * 2;
                final char[] newChars = new char[newCapacity];
                System.arraycopy(oldChars, 0, newChars, 0, oldLength);
                mChars = newChars;
            }
            return oldLength;
        }
    }
}

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值