- 本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發布
- 20181207日更新博客,寫這篇文章的時候,我對簡書的寫作技巧還不熟悉,現在更新下說明
- 寫在前面的話:我就一直糾結啊,這種可以啊,沒毛病啊,老鐵,我就一直做啊做,實現的效果,就是一直Move事件中的筆的寬度都是一樣的,是不是崩潰啊,的確很崩潰,最后我在想,能不能拿到按壓值MotionEvent.getPressure();但是最后通過一查,這個方法的返回值是這樣決定的:感應出用戶的手指壓力,當然具體的級別由驅動和物理硬件決定的,我一直用手寫,這個值永遠不變,奔潰,又一次崩潰,最后在研究一個opengl寫的Demo的時候,我發現了一個真理:那就是,我要畫多長,是用戶手指決定的,但是它的Move事件中接受到的點的數量是和這個距離沒有相對應的關系,啊哈哈,對不對,我接受了這個多點,但是我要畫很長的線,是不是我的線就細了,但Move中的接受到的點數量一樣,我畫的距離短了,是不是線就粗了,這就是這個Demo的原理,頓時豁然開朗,春暖花開!
不逼逼,看效果,感覺我的書法還闊以,哈哈!!
設置筆寬度為60,效果如下
微信圖片_20170910184918.png
image.png
這個效果明顯一點,哈哈,是不是很有大師的寫字風格
微信圖片_20170902142924.jpg
實現這個效果,大體用了40個小時,熬了3天夜,我未來的女朋友給我作證,看了無數的文檔,在git上有個哥們用opengGl3.0實現比我這個更牛逼的效果,但是發現在低端手機上會報錯,原因是不支持openGL3.0,導致Apk裝入失敗,1.0的api有看不懂,你說我能怎么辦,我也很絕望啊!同時感覺opengl更加節手機性能,but我錯了,在低端手機上使用opengl簡直就是噩夢,卡的一逼,算了不提了,此功能的實現還是基于安卓的Paint,通過事件去繪制路徑。
1.創建DrawPenView類繼承View
image.png
初始化筆,筆鋒的效果,我個人嘗試了使用三個筆,每次繪制的時候,三個筆一起繪制,根據手指的滑動速率的快慢去使其中的某個筆不用繪制,但是這個效果稀爛,所以view的還是用一只筆即可,
mPaint = new Paint();
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStrokeWidth(14);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);//結束的筆畫為圓心
mPaint.setStrokeJoin(Paint.Join.ROUND);//連接處元
mPaint.setAlpha(0xFF);
mPaint.setAntiAlias(true);
mPaint.setStrokeMiter(1.0f);
初始化bitmap,和畫布,畫布在這里主要是生成一張bitmap的
private void initParameter(Context context) {
mContext = context;
DisplayMetrics dm = new DisplayMetrics();
((Activity) mContext).getWindowManager().getDefaultDisplay().getMetrics(dm);
mBitmap = Bitmap.createBitmap(dm.widthPixels, dm.heightPixels, Bitmap.Config.ARGB_8888);
//筆的控制類
mVisualStrokePen=new VisualStrokePen(mContext);
initPaint(mContext);
initCanvas();
}
private void initCanvas() {
mCanvas = new Canvas(mBitmap);
//設置畫布的顏色的問題
mCanvas.drawColor(Color.TRANSPARENT);
}
重寫onDraw()方法:由于項目需要,在這里我僅僅提供了兩個方法:清除畫布和繪制。擴展的功能有:返回上一步的繪制步驟,設置畫筆的屬性,mark筆,毛筆,鋼筆,圓珠筆,鉛筆等一切的控制都在這里進行
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
switch (mCanvasCode) {
case CANVAS_NORMAL:
mVisualStrokePen.draw(canvas);
break;
case CANVAS_RESET:
reset();
break;
default:
Log.e(TAG, "onDraw" + Integer.toString(mCanvasCode));
break;
}
super.onDraw(canvas);
}
2.認識MotionEvent對象
當用戶觸摸屏幕時,將創建一個MontionEvent對象。MotionEvent包含了關于發生觸摸的位置和時間的信息,以及觸摸事件的其他細節。
/**
event.getAction() //獲取觸控動作比如ACTION_DOWN
event.getPointerCount(); //獲取觸控點的數量,比如2則可能是兩個手指同時按壓屏幕
event.getPointerId(nID); //對于每個觸控的點的細節,我們可以通過一個循環執行getPointerId方法獲取索引
event.getX(nID); //獲取第nID個觸控點的x位置,記錄的第一個點為getX,getY
event.getY(nID); //獲取第nID個點觸控的y位置
event.getPressure(nID); //LCD可以感應出用戶的手指壓力,當然具體的級別由驅動和物理硬件決定的
event.getDownTime() //按下開始時間
event.getEventTime() // 事件結束時間
event.getEventTime()-event.getDownTime()); //總共按下時花費時間
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
//測試過程中,當使用到event的時候,產生了沒有收到事件的問題,所以在這里需要obtian的一下
MotionEvent event2 = MotionEvent.obtain(event);
switch (event2.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
setCanvasCode(CANVAS_NORMAL);
mVisualStrokePen.onDown(mVisualStrokePen.createMotionElement(event2));
break;
case MotionEvent.ACTION_MOVE:
mVisualStrokePen.onMove(mVisualStrokePen.createMotionElement(event2));
break;
case MotionEvent.ACTION_UP:
long time = System.currentTimeMillis();
mVisualStrokePen.onUp(mVisualStrokePen.createMotionElement(event2),mCanvas);
break;
default:
break;
}
invalidate();
return true;
}
在這里我需要提到一個MotionEvent的api:motionEvent.getToolType(0);返回的以下四種的值,
TOOL_TYPE_UNKNOWN :不知道什么畫的
TOOL_TYPE_FINGER :手指
TOOL_TYPE_STYLUS :筆畫的
TOOL_TYPE_MOUSE :該工具是一個鼠標或觸控板
TOOL_TYPE_ERASER :工具是一塊橡皮或一筆用于倒立的姿勢
看見沒,臥槽,以前都不知道,這個類知道我們用什么屬性在寫字,
event.getPressure(); //可以感應出用戶的手指壓力,當然具體的級別由驅動和物理硬件決定的,我的手機上為1
motionEvent.getEventTime():事件發生的事件,在我此時的事件是shiming==8359650,而且是跟隨著系統的時間而定
···
/**
* Tool type constant: Unknown tool type.
* This constant is used when the tool type is not known or is not relevant,
* such as for a trackball or other non-pointing device.
*
* @see #getToolType
*/
public static final int TOOL_TYPE_UNKNOWN = 0;
/**
* Tool type constant: The tool is a finger.
*
* @see #getToolType
*/
public static final int TOOL_TYPE_FINGER = 1;
/**
* Tool type constant: The tool is a stylus.
*
* @see #getToolType
*/
public static final int TOOL_TYPE_STYLUS = 2;
/**
* Tool type constant: The tool is a mouse or trackpad.
*
* @see #getToolType
*/
public static final int TOOL_TYPE_MOUSE = 3;
/**
* Tool type constant: The tool is an eraser or a stylus being used in an inverted posture.
*
* @see #getToolType
*/
public static final int TOOL_TYPE_ERASER = 4:
關于MotionElement 類:記錄下五個參數:坐標x y,壓力值,什么在屏幕上寫的,還有事件發生的時間。
public static class MotionElement {
public float x;
public float y;
public float pressure;
public int tooltype;
public long timestamp;
public MotionElement(float mx, float my, float mp, int ttype, long mt) {
x = mx;
y = my;
pressure = mp;
tooltype = ttype;
timestamp = mt;
}
}
/**
* event.getPressure(); //LCD可以感應出用戶的手指壓力,當然具體的級別由驅動和物理硬件決定的,我的手機上為1
* @param motionEvent
* @return
*/
public MotionElement createMotionElement(MotionEvent motionEvent) {
System.out.println("shiming== 0000=="+motionEvent.getToolType(0));
System.out.println("shiming=="+motionEvent.getPressure());
System.out.println("shiming=="+motionEvent.getEventTime());
MotionElement motionElement = new MotionElement(motionEvent.getX(), motionEvent.getY(),
motionEvent.getPressure(), motionEvent.getToolType(0),
motionEvent.getEventTime());
return motionElement;
}
3.清除畫布
Xfermode國外有大神稱之為過渡模式,這種翻譯比較貼切但恐怕不易理解,大家也可以直接稱之為圖像混合模式,因為所謂的“過渡”其實就是圖像混合的一種把paint.setXfermode(Xfermode xfermode)的模式設置為clear,使用我們新建的canvas去drapaint這個筆,記得清除完了,要把mode設置為null
有偏文檔介紹的很好,我在這里拋磚引玉一下,就不班門弄斧了:http://www.cnblogs.com/tianzhijiexian/p/4297172.html
/**
*清除畫布,記得清除點的集合
*/
public void reset() {
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mCanvas.drawPaint(mPaint);
mPaint.setXfermode(null);
mVisualStrokePen.clear();
}
4.關于Bezier曲線
先發個圖,嘿嘿,我自己手畫的,看不清沒關系,只需知道4個點的關系,想象一下曲線就行
微信圖片_20170826183403.jpg
image.png
知道兩點連接起來是直線,當我們不斷的求出兩個點的控制點,把無數的控制點繪制在一起就是一條完美的曲線,反正我這樣子理解的,當然我在這里也做了一個width的控制,和這種的原理差不多。
public void init(float lastx, float lasty, float lastWidth, float x, float y, float width)
{
//資源點設置,最后的點的為資源點
mSource.set(lastx, lasty, lastWidth);
float xmid = getMid(lastx, x);
float ymid = getMid(lasty, y);
float wmid = getMid(lastWidth, width);
//距離點為平均點
mDestination.set(xmid, ymid, wmid);
//控制點為當前的距離點
mControl.set(getMid(lastx,xmid),getMid(lasty,ymid),getMid(lastWidth,wmid));
//下個控制點為當前點
mNextControl.set(x, y, width);
}
/**
*
* @param x1 一個點的x
* @param x2 一個點的x
* @return
*/
/**
*
* @param x1 一個點的x
* @param x2 一個點的x
* @return
*/
private float getMid(float x1, float x2) {
return (float)((x1 + x2) / 2.0);
}
private double getWidth(double w0, double w1, double t){
return w0 + (w1 - w0) * t;
}
以上記得知道個步驟,才能方便理解,當這個點是我們資源點的時候,或者是當前點,那么它下一步就會成為一個新的資源點,需要不斷的替換當前的起點和終點,那么才可以形成一個曲線
/**
* 替換就的點,原來的距離點變換為資源點,控制點變為原來的下一個控制點,距離點取原來控制點的和新的的一半
* 下個控制點為新的點
* @param x 新的點的坐標
* @param y 新的點的坐標
* @param width
*/
public void addNode(float x, float y, float width){
mSource.set(mDestination);
mControl.set(mNextControl);
mDestination.set(getMid(mNextControl.x, x), getMid(mNextControl.y, y), getMid(mNextControl.width, width));
mNextControl.set(x, y, width);
}
是不是看不懂,對,看不懂就對了,去下面看代碼,記得在本子上多畫幾個點,想象一下這樣變換的位置,然后就會明白了這真的是一個美妙的曲線,比女朋友還漂亮,哈哈,扯皮了
關于手指抬起來的時候的方法: 結合手指抬起來的動作,告訴現在的曲線控制點也必須變化,其實在這里也不需要結合著up事件使用因為在down的事件中,所有點都會被重置,然后設置這個沒有多少意義,但是可以改變下個事件的朝向改變先留著,因為后面如果需要控制整個顏色的改變的話,我的依靠這個方法,還有按壓的時間的變化
/**
* 結合手指抬起來的動作,告訴現在的曲線控制點也必須變化,其實在這里也不需要結合著up事件使用
* 因為在down的事件中,所有點都會被重置,然后設置這個沒有多少意義,但是可以改變下個事件的朝向改變
* 先留著,因為后面如果需要控制整個顏色的改變的話,我的依靠這個方法,還有按壓的時間的變化
*/
/**
* 結合手指抬起來的動作,告訴現在的曲線控制點也必須變化,其實在這里也不需要結合著up事件使用
* 因為在down的事件中,所有點都會被重置,然后設置這個沒有多少意義,但是可以改變下個事件的朝向改變
* 先留著,因為后面如果需要控制整個顏色的改變的話,我的依靠這個方法,還有按壓的時間的變化
*/
public void end() {
mSource.set(mDestination);
float x = getMid(mNextControl.x, mSource.x);
float y = getMid(mNextControl.y, mSource.y);
float w = getMid(mNextControl.width, mSource.width);
mControl.set(x, y, w);
mDestination.set(mNextControl);
}
還有個方法:我的提一句,是不是想一個一元二次的方程,哈哈!這個不是我寫的,這個是基于git上開源的寫的,是不是有點高中數學的影響了,哈哈,對就是這樣的,
image.png
* 三階曲線的控制點
* @param p0
* @param p1
* @param p2
* @param t
* @return
*/
private double getValue(double p0, double p1, double p2, double t){
double A = p2 - 2 * p1 + p0;
double B = 2 * (p1 - p0);
double C = p0;
return A * t * t + B * t + C;
}
5.關于StrokePen,這個類才是所有的關鍵,如圖分析:其實原理就是,通過安卓事件收集一個點的集合,這個點的集合的第一點和第二個點,繪制一個橢圓一個橢圓。
image.png
private void drawLine(Canvas canvas, double x0, double y0, double w0, double x1, double y1, double w1, Paint paint){
//求兩個數字的平方根 x的平方+y的平方在開方記得X的平方+y的平方=1,這就是一個園
double curDis = Math.hypot(x0-x1, y0-y1);
int steps = 1;
if(paint.getStrokeWidth() < 6){
steps = 1+(int)(curDis/2);
}else if(paint.getStrokeWidth() > 60){
steps = 1+(int)(curDis/4);
}else{
steps = 1+(int)(curDis/3);
}
double deltaX=(x1-x0)/steps;
double deltaY=(y1-y0)/steps;
double deltaW=(w1-w0)/steps;
double x=x0;
double y=y0;
double w=w0;
for(int i=0;i<steps;i++){
RectF oval = new RectF();
oval.set((float)(x-w/4.0f), (float)(y-w/2.0f), (float)(x+w/4.0f), (float)(y+w/2.0f));
//最基本的實現,通過點控制線,繪制橢圓
canvas.drawOval(oval, paint);
x+=deltaX;
y+=deltaY;
w+=deltaW;
}
}
說明
- 求兩個數字的平方根 x的平方+y的平方在開方記得X的平方+y的平方=1,這就是一個園
double curDis = Math.hypot(x0-x1, y0-y1); - 繪制多少個橢圓,我們可以根據筆的寬度,當筆的寬度和大的時候,我們繪制的可以適當減少步驟
if(paint.getStrokeWidth() < 6){
steps = 1+(int)(curDis/2);
}else if(paint.getStrokeWidth() > 60){
steps = 1+(int)(curDis/4);
}else{
steps = 1+(int)(curDis/3);
}
- 關于Rext和RexF的區別:Rect是使用int類型作為數值,RectF是使用float類型作為數值。很明顯這里我們需要更高的精度
- 繪制的原理,就是每個記錄下的點繪制一個橢圓,當無數個的橢圓重合在一起就是一個線,這個線的寬度和橢圓的形狀有關系
RectF oval = new RectF();
oval.set((float)(x-w/4.0f), (float)(y-w/2.0f), (float)(x+w/4.0f), (float)(y+w/2.0f));
//最基本的實現,通過點控制線,繪制橢圓
canvas.drawOval(oval, paint);
這里需要在view中的onDraw中調用,本來我開始是想說能不能再一開始的時候,down事件的時候,給他畫個園,但是這個園的半徑我控制不好,所以在代碼中我留下這個問題,以后需要做更難的效果的時候,我來把這個開始的步驟補上。
/**
* 早onDraw需要調用
* @param canvas 畫布
*/
public void draw(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL);
//點的集合少 不去繪制
if (mHWPointList == null || mHWPointList.size() < 1)
return;
//當控制點的集合很少的時候,需要畫個小圓,但是需要算法
if (mHWPointList.size() < 2) {
ControllerPoint point = mHWPointList.get(0);
//由于此問題在算法上還沒有實現,所以暫時不給他畫圓圈
//canvas.drawCircle(point.x, point.y, point.width, mPaint);
} else {
curPoint = mHWPointList.get(0);
for (int i = 1; i < mHWPointList.size(); i++) {
ControllerPoint point = mHWPointList.get(i);
drawToPoint(canvas, point, mPaint);
curPoint = point;
}
}
}
Down事件處理
* 手指的down事件
* @param mElement
*/
public void onDown(MotionElement mElement) {
mPaint.setXfermode(null);
mPath = new Path();
mPointList.clear();
mHWPointList.clear();
//記錄down的控制點的信息
ControllerPoint curPoint = new ControllerPoint(mElement.x, mElement.y);
//如果用筆畫的畫我的屏幕,記錄他寬度的和壓力值的乘,但是哇,
if (mElement.tooltype == MotionEvent.TOOL_TYPE_STYLUS) {
mLastWidth = mElement.pressure * mBaseWidth;
} else {
//如果是手指畫的,我們取他的0.8
mLastWidth = 0.8 * mBaseWidth;
}
//down下的點的寬度
curPoint.width = (float) mLastWidth;
mLastVel = 0;
mPointList.add(curPoint);
//記錄當前的點
mLastPoint = curPoint;
//繪制起點
mPath.moveTo(mElement.x, mElement.y);
}
···
Move事件的處理
public void onMove(MotionElement mElement) {
ControllerPoint curPoint = new ControllerPoint(mElement.x, mElement.y);
double deltaX = curPoint.x - mLastPoint.x;
double deltaY = curPoint.y - mLastPoint.y;
//deltaX和deltay平方和的二次方根 想象一個例子 1+1的平方根為1.4 (x2+y2)開根號
double curDis = Math.hypot(deltaX, deltaY);
//我們求出的這個值越小,畫的點或者是繪制橢圓形越多,這個值越大的話,繪制的越少,筆就越細,寬度越小
double curVel = curDis * DIS_VEL_CAL_FACTOR;
System.out.println("shiming==="+curDis+" "+curVel+" "+deltaX+" "+deltaY);
double curWidth;
//點的集合少,我們得必須改變寬度,每次點擊的down的時候,這個事件
if (mPointList.size() < 2) {
System.out.println("shiming==dian shao");
if (mElement.tooltype == MotionEvent.TOOL_TYPE_STYLUS) {
curWidth = mElement.pressure * mBaseWidth;
} else {
curWidth = calcNewWidth(curVel, mLastVel, curDis, 1.5,
mLastWidth);
}
curPoint.width = (float) curWidth;
mBezier.Init(mLastPoint, curPoint);
} else {
System.out.println("shiming==dian duo");
mLastVel = curVel;
if (mElement.tooltype == MotionEvent.TOOL_TYPE_STYLUS) {
curWidth = mElement.pressure * mBaseWidth;
} else {
//由于我們手機是觸屏的手機,滑動的速度也不慢,所以,一般會走到這里來
//闡明一點,當滑動的速度很快的時候,這個值就越小,越慢就越大,依靠著mlastWidth不斷的變換
curWidth = calcNewWidth(curVel, mLastVel, curDis, 1.5,
mLastWidth);
System.out.println("shiming=="+curVel+" "+mLastVel+" "+curDis+" " +mLastWidth);
System.out.println("shiming==dian duo"+curWidth);
}
curPoint.width = (float) curWidth;
mBezier.AddNode(curPoint);
}
//每次移動的話,這里賦值新的值
mLastWidth = curWidth;
mPointList.add(curPoint);
int steps = 1 + (int) curDis / STEPFACTOR;
System.out.println("shiming-- steps"+steps);
double step = 1.0 / steps;
for (double t = 0; t < 1.0; t += step) {
ControllerPoint point = mBezier.GetPoint(t);
mHWPointList.add(point);
}
mPath.quadTo(mLastPoint.x, mLastPoint.y,
(mElement.x + mLastPoint.x) / 2,
(mElement.y + mLastPoint.y) / 2);
mLastPoint = curPoint;
}
Up事件的處理:當需要關心我們畫的這個bitmap的時候,記得在up結束的時候,需要把這個繪制的東西需要重新繪制到我們自定義View的畫布上,這個畫筆是自己定義的,而不是View里面onDraw(cavns)里面的畫布
public void onUp(MotionElement mElement, Canvas canvas) {
ControllerPoint curPoint = new ControllerPoint(mElement.x, mElement.y);
double deltaX = curPoint.x - mLastPoint.x;
double deltaY = curPoint.y - mLastPoint.y;
double curDis = Math.hypot(deltaX, deltaY);
if (mElement.tooltype == MotionEvent.TOOL_TYPE_STYLUS) {
curPoint.width = (float) (mElement.pressure * mBaseWidth);
} else {
curPoint.width = 0;
}
mPointList.add(curPoint);
mBezier.AddNode(curPoint);
int steps = 1 + (int) curDis / STEPFACTOR;
double step = 1.0 / steps;
for (double t = 0; t < 1.0; t += step) {
ControllerPoint point = mBezier.GetPoint(t);
mHWPointList.add(point);
}
//
mBezier.End();
for (double t = 0; t < 1.0; t += step) {
ControllerPoint point = mBezier.GetPoint(t);
mHWPointList.add(point);
}
mPath.quadTo(mLastPoint.x, mLastPoint.y,
(mElement.x + mLastPoint.x) / 2,
(mElement.y + mLastPoint.y) / 2);
mPath.lineTo(mElement.x, mElement.y);
// 手指up 我畫到紙上上
draw(canvas);
}
其實這里才是關鍵的地方,通過畫布畫橢圓,每一個點都是一個橢圓,這個橢圓的所有細節,逐漸構建出一個完美的筆尖 和筆鋒的效果,我覺得在這里需要大量的測試,其實就對低端手機進行排查,看我們繪制的筆的寬度是多少,繪制多少個橢圓然后在低端手機上不會那么卡,當然你哪一個N年前的手機給我,那也的卡,只不過需要適中的范圍里面
private void drawLine(Canvas canvas, double x0, double y0, double w0, double x1, double y1, double w1, Paint paint){
double curDis = Math.hypot(x0-x1, y0-y1);
int steps = 1;
if(paint.getStrokeWidth() < 6){
steps = 1+(int)(curDis/2);
}else if(paint.getStrokeWidth() > 60){
steps = 1+(int)(curDis/4);
}else{
steps = 1+(int)(curDis/3);
}
double deltaX=(x1-x0)/steps;
double deltaY=(y1-y0)/steps;
double deltaW=(w1-w0)/steps;
double x=x0;
double y=y0;
double w=w0;
for(int i=0;i<steps;i++){
RectF oval = new RectF();
oval.set((float)(x-w/4.0f), (float)(y-w/2.0f), (float)(x+w/4.0f), (float)(y+w/2.0f));
//最基本的實現,通過點控制線,繪制橢圓
canvas.drawOval(oval, paint);
x+=deltaX;
y+=deltaY;
w+=deltaW;
}
}
最后來張自畫像,可以,帥的一比!
image.png
寫在最后的話,不要皮,講個原理,實現筆鋒的效果?到底怎么實現,我先前糾結的是我一定要拿到手指的滑動的速率,安卓也提供了這個api,ViewPager的源碼中提供了思路:如下圖所示
image.png
image.png
image.png
- 當這個速度大于了mMinimumVelocity這個值的時候, Math.abs(velocity) > mMinimumVelocity那么我們的頁面就需要翻頁了,下面是ViewPager的實現的代碼,很明顯就知道
final float density = context.getResources().getDisplayMetrics().density;
mTouchSlop = configuration.getScaledPagingTouchSlop();
mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
int targetPage;
if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
targetPage = velocity > 0 ? currentPage : currentPage + 1;
} else {
final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
targetPage = currentPage + (int) (pageOffset + truncator);
}
if (mItems.size() > 0) {
final ItemInfo firstItem = mItems.get(0);
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
// Only let the user target pages we have items for
targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
}
return targetPage;
}
- 我就一直糾結啊,這種可以啊,沒毛病啊,老鐵,我就一直做啊做,實現的效果,就是一直Move事件中的筆的寬度都是一樣的,是不是崩潰啊,的確很崩潰,最后我在想,能不能拿到按壓值MotionEvent.getPressure();但是最后通過一查,這個方法的返回值是這樣決定的:感應出用戶的手指壓力,當然具體的級別由驅動和物理硬件決定的,我一直用手寫,這個值永遠不變,奔潰,又一次崩潰,最后在研究一個opengl寫的Demo的時候,我發現了一個真理:那就是,我要畫多長,是用戶手指決定的,但是它的Move事件中接受到的點的數量是和這個距離沒有相對應的關系,啊哈哈,對不對,我接受了這個多點,但是我要畫很長的線,是不是我的線就細了,但Move中的接受到的點數量一樣,我畫的距離短了,是不是線就粗了,這就是這個Demo的原理,頓時豁然開朗,春暖花開!
- 最后奉上Git地址,求贊,如果后續有空閑的時間,我會試著實現毛筆的效果和馬克筆的效果,求star,謝謝!
WritingPen