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的速度矢量直线
*
* {@link PLView#onPointerEvent(MotionEvent)} 此方法是记录整个TouchEvent的过程
* Exp:
* {@link PLView#mPointers} 屏幕上一共有几条触摸轨迹(一个完整的事件流的集合)记录在这个集合里
* 集合中的数据类型是{@link PointerState}(一个完整的事件流)
* 几个关键的属性
*
* 以下三个变量用于实际触摸曲线的绘制
* {@link PointerState#mTraceX x轨迹点的坐标}
* {@link PointerState#mTraceY y轨迹点的坐标}
* {@link PointerState#mTraceCurrent 经过的轨迹点:false 最后的触摸点:true 两种类型的点的坐标可能会有重合}
*
* 用以下的两个变量来绘制速度矢量直线 这两个值的获取依赖 {@link PLView#mVelocityTracker}
* {@link PointerState#mXVelocity} x分量速度
* {@link PointerState#mYVelocity} y分量速度
*
* 依靠下面的这个变量来绘制估计运动曲线 这个值的获取依赖于 {@link PLView#mVelocityTracker}
* {@link PointerState#mEstimator} 位置估计变量 基于多项式模型的指针运动估计器
*
* {@link PLView#mVelocityTracker} 事件的速度追踪
*
* {@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 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;
}
}
}