首先来看一下咱酷炫的效果图吧!由于是在模拟器上运行,鼠标拖动操作的,所以写的字较丑,见谅见谅哈!
这个签名板涉及到以下几个知识点:
一、自定义view的制作
二、将布局存成图像并保存
我们来简单说说自定义view:
首先我们要明白,为什么要自定义View?
主要是Android系统内置的View无法实现我们的需求,我们需要针对我们的业务需求定制我们想要的View。自定义View我们大部分时候只需重写两个函数:onMeasure()、onDraw()。onMeasure负责对当前View的尺寸进行测量,onDraw负责把当前这个View绘制出来。当然了,你还得写至少写2个构造函数:
//这个方法的调用环境,例如在activity中,在onCreate()方法里,setContentView(new MyView(this));
//这样直接new自定义的View的时候调用
public MyView(Context context) {
super(context);
}
//这个方法的调用环境,是在布局文件 XXX.xml 中我们用到这个View时调用,也可以看下面的案例
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
onMeasure()这个方法也是自定义View需要重写的,
View类的默认处理不满足我们的要求的话,我们就得重写onMeasure函数。我们可以给自己的View定义一个在width和height在wrap_content下一个默认值,如果width为300dp,height为250dp。就应该像以下这么写:
private int getSize(int sizedp,int measureSpec){
int finalsize = 0;
int mode=MeasureSpec.getMode(measureSpec);//提取测量模式
int size = MeasureSpec.getSize(measureSpec);//提取尺寸
switch (mode){
case MeasureSpec.AT_MOST://表示子布局限制在一个最大值内,一般为WARP_CONTENT
finalsize=sizedp*density;
break;
case MeasureSpec.EXACTLY://一般是设置了明确的值或者是MATCH_PARENT
finalsize=size;
break;
case MeasureSpec.UNSPECIFIED://没有指定大小,就设为默认大小,表示子布局想要多大就多大,很少使用
finalsize=sizedp*density;//此处的density我后面会讲到
break;
}
return finalsize;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getSize(300,widthMeasureSpec),getSize(250,heightMeasureSpec));
}
画图的话要重写View中的onDraw方法:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mycanvas=canvas;//这个是画布
Paint paint=new Paint();//画笔
paint.setColor(Color.BLACK);//设置画笔的颜色
paint.setStrokeWidth(density);//设置画笔画出线的粗细,
paint.setAntiAlias(true);//防锯齿,使画出的线条更加的圆滑
paint.setDither(true);//防抖动
paint.setStyle(Paint.Style.STROKE);//设置画笔为空心
paint.setStrokeCap(Paint.Cap.ROUND);//设置线冒样式,取值有Cap.ROUND(圆形线冒)、Cap.SQUARE(方形线冒)、Paint.Cap.BUTT(无线冒)
paint.setStrokeJoin(Paint.Join.MITER);//设置线段连接处样式,取值有:Join.MITER(结合处为锐角)、Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)
canvas.drawPath(path,paint);//根据路径画线
}
Path有几个方法:moveTo,lineTo,quadTo,cubicTo,arcTo
1.moveTo 不会进行绘制,只用于移动移动画笔。
2.lineTo 用于进行直线绘制。
3、quadTo 用于绘制圆滑曲线,即贝塞尔曲线
mPath.quadTo(x1, y1, x2, y2) (x1,y1) 为控制点,(x2,y2)为结束点。 控制点从字面上就表达清楚了,就是一条直线,根据控制点的位置进行弯曲,形成曲线
4、cubicTo 同样是用来实现贝塞尔曲线的。
mPath.cubicTo(x1, y1, x2, y2, x3, y3) (x1,y1) 为控制点,(x2,y2)为控制点,(x3,y3) 为结束点。
那么,cubicTo 和 quadTo 有什么不一样呢?
就是多了一个控制点而已。
然后,我们想绘制和上一个一样的曲线,应该怎么写呢?
mPath.moveTo(100, 500);
mPath.quadTo(300, 100, 600, 500);
canvas.drawPath(mPath, mPaint);
如果我们不加 moveTo 呢?
则以(0,0)为起点,(100,500)和(300,100)为控制点绘制贝塞尔曲线。
5、arcTo 用于绘制弧线(实际是截取圆或椭圆的一部分)。
mPath.arcTo(ovalRectF, startAngle, sweepAngle) , ovalRectF为椭圆的矩形,startAngle 为开始角度,sweepAngle 为结束角度。
mRectF = new RectF(10, 10, 600, 600);
mPath.arcTo(mRectF, 0, 90);
canvas.drawPath(mPath, mPaint);
由于new RectF(10, 10, 600, 600)为正方形,又截取 0 ~ 90 度 ,则所得曲线为四分之一圆的弧线。
然后我们来说下前面提到的density:
具体可以参考下这个帖子http://www.cnblogs.com/yaozhongxiao/archive/2014/07/14/3842908.htmldpi :dots per inch , 直接来说就是一英寸多少个像素点。常见取值 120,160,240。我一般称作像素密度,简称密度
density : 直接翻译的话貌似叫 密度。常见取值 1.5 , 1.0 。和标准dpi的比例(160px/inc)
在android里面,获取一个窗口的metrics,里面有这么几个值
metrics.density; 其实是 DPI / 160 后得到的值。
metrics.densityDpi;就是我们常说的dpi。
我们写布局的时候,肯定还是要知道1个dp到底有多少px的。
换算公式如下: dp = (DPI/(160像素/英寸))px = density px
注意,这里都是带单位的。px是单位,dp是单位,density没单位。
为了方便,假设dpi是240 像素/英寸 , 那么density就是1.5
那么就是 dp=1.5px ,注意这是带了单位的,也就是 设备无关像素 = density 像素
那么转换为数值计算的话,应该是下面这个式子
PX = density * DP
最后,我们来看看怎么样将布局存成图像
首先要调用view.setDrawingCacheEnabled方法打开图像缓存,然后使用view.getDrawingCache 方法获取View的Bitmap对象。保存成jpg图像使用Bitmap.compress方法即可。
好,接下来,上代码:
自定义View:
package com.example.my_signature_app;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class MySignatureView extends View {
Canvas mycanvas;
int density;
float startx;
float starty;
Path path;
public MySignatureView(Context context) {
super(context);
}
public MySignatureView(Context context, AttributeSet attrs) {
super(context, attrs);
density= (int)context.getResources().getDisplayMetrics().density;
path=new Path();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getSize(300,widthMeasureSpec),getSize(250,heightMeasureSpec));
}
private int getSize(int sizedp,int measureSpec){
int finalsize = 0;
int mode=MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode){
case MeasureSpec.AT_MOST:
finalsize=sizedp*density;
break;
case MeasureSpec.EXACTLY:
finalsize=size;
break;
case MeasureSpec.UNSPECIFIED:
finalsize=sizedp*density;
break;
}
return finalsize;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
drawStart(event.getX(),event.getY());
break;
case MotionEvent.ACTION_MOVE:
drawMove(event.getX(),event.getY());
break;
case MotionEvent.ACTION_UP:
break;
}
invalidate();
return true;
}
private void drawStart(float x, float y) {
path.moveTo(x, y);
startx=x;starty=y;
}
private void drawMove(float x,float y){
path.quadTo(startx,starty,(startx+x)/2,(starty+y)/2);//曲线终点设置为startx/starty和x/y的一半,这样画出的线更圆滑
startx=x;starty=y;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mycanvas=canvas;
Paint paint=new Paint();
paint.setColor(Color.BLACK);
paint.setStrokeWidth(density);
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);//设置画笔为空心
paint.setStrokeCap(Paint.Cap.ROUND);//设置线冒样式,取值有Cap.ROUND(圆形线冒)、Cap.SQUARE(方形线冒)、Paint.Cap.BUTT(无线冒)
paint.setStrokeJoin(Paint.Join.MITER);//设置线段连接处样式,取值有:Join.MITER(结合处为锐角)、Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)
canvas.drawPath(path,paint);
}
}
Activity:
public class MainActivity extends AppCompatActivity {
private Button btn;
private ImageView imageView;
private String path="/sdcard/test.jpg";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn=(Button)findViewById(R.id.signature_btn);
imageView=(ImageView)findViewById(R.id.signature_iv);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showDialog();
}
});
}
private void showDialog(){
final Dialog dialog=new Dialog(MainActivity.this);
dialog.setTitle("请签名");
final Window window=dialog.getWindow();
window.setContentView(R.layout.dialog);
Button cancel=(Button)window.findViewById(R.id.cancel_btn);
Button ok=(Button)window.findViewById(R.id.ok_btn);
final MySignatureView signatureview=(MySignatureView)window.findViewById(R.id.signature);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
ok.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
signatureview.setDrawingCacheEnabled(true);//打开图像缓存
Bitmap bitmap=signatureview.getDrawingCache();
try{
//获取可视组件的截图
//将截图保存在SD卡根目录的test.png图像文件中
File file=new File(path);
if(file.exists()){
file.delete();
}
FileOutputStream fos=new FileOutputStream(path);
//将bitmap对象中的图像数据压缩成png格式的图像数据,并将这些数据保存在test.png文件中
bitmap.compress(Bitmap.CompressFormat.PNG,100,fos);
//关闭文件流输出
fos.close();
}catch (Exception e){
Log.e("error",e.getMessage());
}
Bitmap bm = BitmapFactory.decodeFile(path);
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int screenWidth=dm.widthPixels;
if(bm.getWidth()<=screenWidth){
imageView.setImageBitmap(bm);
}else{
Bitmap bmp=Bitmap.createScaledBitmap(bm, screenWidth, bm.getHeight()*screenWidth/bm.getWidth(), true);
imageView.setImageBitmap(bmp);
}
dialog.dismiss();
}
});
dialog.show();
}
}
Activity的布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.my_signature_app.MainActivity">
<ImageView
android:id="@+id/signature_iv"
android:layout_width="350dp"
android:layout_height="300dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" />
<Button
android:id="@+id/signature_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:background="@android:color/holo_blue_dark"
android:text="点我签名哟"
android:textColor="@android:color/white" />
</RelativeLayout>
签名板弹窗的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.example.my_signature_app.MySignatureView
android:id="@+id/signature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="#ffffff" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_above="@+id/signature"
android:background="#c8c8c8" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/signature"
android:layout_marginBottom="30dp"
android:layout_marginTop="20dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/cancel_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="40dp"
android:text="取消" />
<Button
android:id="@+id/ok_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定" />
</LinearLayout>
</LinearLayout>
看源码请点击这里:http://download.youkuaiyun.com/detail/aa_chao/9695364