activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.imooc.guaguaka.view.GuaGuaKa android:layout_width="fill_parent" android:layout_height="fill_parent" /> </RelativeLayout>
package com.imooc.guaguaka; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
package com.imooc.guaguaka.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class GuaGuaKa extends View { /** 内存中创建的Canvas */ private Canvas mCanvas; /** 绘制线条的Paint,即用户手指绘制Path */ private Paint mOutterPaint = new Paint(); /** 记录用户绘制的Path */ private Path mPath = new Path(); /** mCanvas绘制内容在其上 */ private Bitmap mBitmap; private int mLastX; private int mLastY; /** 绘制字体 */ private Paint mBackPint = new Paint(); private Rect mTextBound = new Rect(); private String mText = "500,0000,000"; public GuaGuaKa(Context context) { this(context, null); } public GuaGuaKa(Context context, AttributeSet attrs) { this(context, attrs, 0); } public GuaGuaKa(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); int height = getMeasuredHeight(); mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); // 在canvas初始化的时候就传入了一个空的bitmap // 最后canvas中绘画的内容都被绘制到了bitmap中,从而得到了我们需要的bitmap mCanvas = new Canvas(mBitmap); setUpBackPaint(); setOutPaint(); // 表面绘制一层灰色 mCanvas.drawColor(Color.parseColor("#c0c0c0")); } private void setOutPaint() { // 设置画笔 mOutterPaint.setColor(Color.RED); mOutterPaint.setAntiAlias(true); mOutterPaint.setDither(true); mOutterPaint.setStyle(Paint.Style.STROKE); mOutterPaint.setStrokeJoin(Paint.Join.ROUND); // 圆角 mOutterPaint.setStrokeCap(Paint.Cap.ROUND); // 圆角 mOutterPaint.setStrokeWidth(20); } private void setUpBackPaint() { mBackPint.setStyle(Style.FILL); mBackPint.setTextScaleX(2f); mBackPint.setColor(Color.DKGRAY); mBackPint.setTextSize(22); // getTextBounds(String text, int start, int end, Rect bounds) mBackPint.getTextBounds(mText, 0, mText.length(), mTextBound); } @Override protected void onDraw(Canvas canvas) { canvas.drawText(mText, getWidth() / 2 - mTextBound.width() / 2, getHeight() / 2 + mTextBound.height() / 2, mBackPint); mOutterPaint .setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); mCanvas.drawPath(mPath, mOutterPaint); canvas.drawBitmap(mBitmap, 0, 0, null); } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); int x = (int) event.getX(); int y = (int) event.getY(); switch (action) { case MotionEvent.ACTION_DOWN: mLastX = x; mLastY = y; mPath.moveTo(mLastX, mLastY); break; case MotionEvent.ACTION_MOVE: mPath.lineTo(x, y); mLastX = x; mLastY = y; break; } invalidate(); return true; } }
*************************************************二*******************************************
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFF" android:orientation="vertical" > <com.aigestudio.customviewdemo.views.EraserView android:id="@+id/main_cv" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
package com.aigestudio.customviewdemo.activities; import android.app.Activity; import android.os.Bundle; import com.aigestudio.customviewdemo.R; /** * 主界面 */ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
package com.aigestudio.customviewdemo.utils; import android.app.Activity; import android.util.DisplayMetrics; /** * 测绘工具类 */ public final class MeasureUtil { /** * 获取屏幕尺寸 * * @param activity * Activity * @return 屏幕尺寸像素值,下标为0的值为宽,下标为1的值为高 */ public static int[] getScreenSize(Activity activity) { DisplayMetrics metrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); return new int[] { metrics.widthPixels, metrics.heightPixels }; } }
package com.aigestudio.customviewdemo.views; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import com.aigestudio.customviewdemo.R; import com.aigestudio.customviewdemo.utils.MeasureUtil; /** * 橡皮檫View */ public class EraserView extends View { private static final int MIN_MOVE_DIS = 5;// 最小的移动距离:如果我们手指在屏幕上的移动距离小于此值则不会绘制 private Bitmap fgBitmap, bgBitmap;// 前景橡皮擦的Bitmap和背景我们底图的Bitmap private Canvas mCanvas;// 绘制橡皮擦路径的画布 private Paint mPaint;// 橡皮檫路径画笔 private Path mPath;// 橡皮擦绘制路径 private int screenW, screenH;// 屏幕宽高 private float preX, preY;// 记录上一个触摸事件的位置坐标 public EraserView(Context context, AttributeSet set) { super(context, set); // 计算参数 cal(context); // 初始化对象 init(context); } /** * 计算参数 * * @param context * 上下文环境引用 */ private void cal(Context context) { // 获取屏幕尺寸数组 int[] screenSize = MeasureUtil.getScreenSize((Activity) context); // 获取屏幕宽高 screenW = screenSize[0]; screenH = screenSize[1]; } /** * 初始化对象 */ private void init(Context context) { // 实例化路径对象 mPath = new Path(); // 实例化画笔并开启其抗锯齿和抗抖动 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); // 设置画笔透明度为0是关键!我们要让绘制的路径是透明的,然后让该路径与前景的底色混合“抠”出绘制路径 mPaint.setARGB(128, 255, 0, 0); // 设置混合模式为DST_IN mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); // 设置画笔风格为描边 mPaint.setStyle(Paint.Style.STROKE); // 设置路径结合处样式 mPaint.setStrokeJoin(Paint.Join.ROUND); // 设置笔触类型 mPaint.setStrokeCap(Paint.Cap.ROUND); // 设置描边宽度 mPaint.setStrokeWidth(50); // 生成前景图Bitmap fgBitmap = Bitmap.createBitmap(screenW, screenH, Config.ARGB_8888); // 将其注入画布 mCanvas = new Canvas(fgBitmap); // 绘制画布背景为中性灰 mCanvas.drawColor(0xFF808080); // 获取背景底图Bitmap bgBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a4); // 缩放背景底图Bitmap至屏幕大小 bgBitmap = Bitmap.createScaledBitmap(bgBitmap, screenW, screenH, true); } @Override protected void onDraw(Canvas canvas) { // 绘制背景 canvas.drawBitmap(bgBitmap, 0, 0, null); // 绘制前景 canvas.drawBitmap(fgBitmap, 0, 0, null); /* * 这里要注意canvas和mCanvas是两个不同的画布对象 * 当我们在屏幕上移动手指绘制路径时会把路径通过mCanvas绘制到fgBitmap上 * 每当我们手指移动一次均会将路径mPath作为目标图像绘制到mCanvas上,而在上面我们先在mCanvas上绘制了中性灰色 * 两者会因为DST_IN模式的计算只显示中性灰,但是因为mPath的透明,计算生成的混合图像也会是透明的 * 所以我们会得到“橡皮擦”的效果 */ mCanvas.drawPath(mPath, mPaint); } /** * View的事件将会在7/12详解 */ @Override public boolean onTouchEvent(MotionEvent event) { /* * 获取当前事件位置坐标 */ float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN:// 手指接触屏幕重置路径 mPath.reset(); mPath.moveTo(x, y); preX = x; preY = y; break; case MotionEvent.ACTION_MOVE:// 手指移动时连接路径 float dx = Math.abs(x - preX); float dy = Math.abs(y - preY); if (dx >= MIN_MOVE_DIS || dy >= MIN_MOVE_DIS) { mPath.quadTo(preX, preY, (x + preX) / 2, (y + preY) / 2); preX = x; preY = y; } break; } // 重绘视图 invalidate(); return true; } }