zoukankan      html  css  js  c++  java
  • 【Android】自己定义View、画家(画布)Canvas与画笔Paint的应用——绘图、涂鸦板app的实现

    利用一个简单的绘图app来说明安卓的图形处理类与自己定义View的应用。

    例如以下图,有一个供用户自己随意绘图、涂鸦的app。


    这里不做那么花俏了,仅提供黑白两色。但能够改变笔尖的粗细。

    实质上这里的橡皮擦就是白色的画笔,根本不用使用到画笔的setXfermode方法,要搞一堆复杂的project。

    用户画完图之后能够保存图像。图像的文件名称是当前的时间。保存的位置是sdcard的根文件夹。

    制作步骤例如以下:

    1、先设置好字体文件resvaluesstrings.xml,主要是app的名称与菜单各个子项的字符。

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <string name="app_name">绘图</string>
        <string name="menu1">画笔宽度</string>
        <string name="menu1_sub1">1</string>
        <string name="menu1_sub2">5</string>
        <string name="menu1_sub3">10</string>
        <string name="menu1_sub4">50</string>
        <string name="menu2">画笔</string>
        <string name="menu3">橡皮擦</string>
        <string name="menu4">保存</string>
        <string name="menu5">退出</string>
        <string name="menu_author">作者:yongh701</string>
    
    </resources>
    2、之后就是菜单文件的设置,这里不再赘述了,在《【Android】日期拾取器、时间拾取器与菜单》(点击打开链接)与《【Android】app透明与字体颜色更变、上下文菜单》(点击打开链接)两篇文章都具体搞过菜单的东西。主要是第一个菜单选项“画笔宽度”是带有子项的,因此,设置菜单的id是分别给子项设置id。而不是主项。主项无须id。

    <menu xmlns:android="http://schemas.android.com/apk/res/android" >
    
        <item android:title="@string/menu1">
            <menu>
                <group android:checkableBehavior="single" >
                    <item
                        android:id="@+id/menu1_sub1"
                        android:title="@string/menu1_sub1"/>
                    <item
                        android:id="@+id/menu1_sub2"
                        android:title="@string/menu1_sub2"/>
                    <item
                        android:id="@+id/menu1_sub3"
                        android:title="@string/menu1_sub3"/>
                    <item
                        android:id="@+id/menu1_sub4"
                        android:title="@string/menu1_sub4"/>
                </group>
            </menu>
        </item>
        <item
            android:id="@+id/menu2"
            android:title="@string/menu2"/>
        <item
            android:id="@+id/menu3"
            android:title="@string/menu3"/>
        <item
            android:id="@+id/menu4"
            android:title="@string/menu4"/>
        <item
            android:id="@+id/menu5"
            android:title="@string/menu5"/>
        <item android:title="@string/menu_author"/>
    
    </menu>

    3、因为一会儿还要把用户画出来的图片写入的sdcard卡,因此将在AndroidManifest.xml申请sdcard的写入的权限:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.painter"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="8"
            android:targetSdkVersion="18" />
    	<!-- 须要在SD卡写入数据的权限 -->
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            <activity
                android:name="com.painter.MainActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>

    4、之后就像《【Android】自己定义View、画布Canvas与画笔Paint》(点击打开链接)一样,新建一个自己定义的View。这里是DrawView。这个DrawView是本app实现的核心。其构造方法,使用public DrawView(Context context, AttributeSet attrs) {super(context, attrs);} ,这个带有两个參数的构造方法,由于一会儿这个DrawView将以xml的方式直接布置在MainActivity。同一时候通过Alt+Shift+S->V选择继承protected void onDraw(Canvas canvas) {},public boolean onTouchEvent(MotionEvent event) {}这两个方法,一个是安卓图像处理技术的基本方法onDraw,一个是用户触摸这个View时发生的事件onTouchEvent方法。同一时候自己加入一个saveBitmap方法,用来实现图片的终于的保存。

    DrawView.java的代码例如以下:

    package com.painter;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Locale;
    
    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.Path;
    import android.os.Environment;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.Toast;
    
    public class DrawView extends View {
    	private Bitmap cacheBitmap;// 画纸
    	private Canvas cacheCanvas;// 创建画布、画家
    	private Path path;// 画图的路径
    	public Paint paint;// 画笔
    	private float preX, preY;// 之前的XY的位置。用于以下的手势移动
    	private int view_width, view_height;// 屏幕的高度与宽度
    
    	public DrawView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		path = new Path();
    		paint = new Paint();
    		cacheCanvas = new Canvas();
    		// 获取屏幕的高度与宽度
    		view_width = context.getResources().getDisplayMetrics().widthPixels;
    		view_height = context.getResources().getDisplayMetrics().heightPixels;
    		cacheBitmap = Bitmap.createBitmap(view_width, view_height,
    				Config.ARGB_8888);// 建立图像缓冲区用来保存图像
    		cacheCanvas.setBitmap(cacheBitmap);
    		cacheCanvas.drawColor(Color.WHITE);
    		paint.setColor(Color.BLACK);// 设置画笔的默认颜色
    		paint.setStyle(Paint.Style.STROKE);// 设置画笔的填充方式为无填充、不过画线
    		paint.setStrokeWidth(1);// 设置画笔的宽度为1
    
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas) {
    		super.onDraw(canvas);
    		canvas.drawBitmap(cacheBitmap, 0, 0, paint);// 把cacheBitmap画到DrawView上
    	}
    
    	@Override
    	public boolean onTouchEvent(MotionEvent event) {
    
    		// 获取触摸位置
    		float x = event.getX();
    		float y = event.getY();
    		switch (event.getAction()) {// 获取触摸的各个瞬间
    		case MotionEvent.ACTION_DOWN:// 手势按下
    			path.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 > 5 || dy > 5) {// 用户要移动超过5像素才算是画图。免得手滑、手抖现象
    				path.quadTo(preX, preY, (x + preX) / 2, (y + preY) / 2);
    				preX = x;
    				preY = y;
    				cacheCanvas.drawPath(path, paint);// 绘制路径
    			}
    			break;
    		case MotionEvent.ACTION_UP:
    			path.reset();
    			break;
    		}
    		invalidate();
    		return true;
    	}
    
    	public void saveBitmap() throws Exception {
    
    		String sdpath = Environment.getExternalStorageDirectory()
    				.getAbsolutePath();// 获取sdcard的根路径
    		String filename = new SimpleDateFormat("yyyyMMddhhmmss",
    				Locale.getDefault())
    				.format(new Date(System.currentTimeMillis()));// 产生时间戳,称为文件名称
    		File file = new File(sdpath + File.separator + filename + ".png");
    		file.createNewFile();
    		FileOutputStream fileOutputStream = new FileOutputStream(file);
    		cacheBitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);// 以100%的品质创建png
    		// 人走带门
    		fileOutputStream.flush();
    		fileOutputStream.close();
    		Toast.makeText(getContext(),
    				"图像已保存到" + sdpath + File.separator + filename + ".png",
    				Toast.LENGTH_SHORT).show();
    
    	}
    
    }
    

    整个DrawView.java做了例如以下的事情:

    (1)设置一张画纸cacheBitmap。两个画家Canvas与cacheCanvas,一支画笔paint,这里之所以要有两位画家,是由于onDraw方法独占Canvas这个类成员。

    用户每触摸一次屏幕。都会触发onTouchEvent方法。设置cacheCanvas把用户触摸时绘制的路径放到画纸cacheBitmap上。通过invalidate();方法的调用再次onDraw方法,Canvas把画纸cacheBitmap放到DrawView这个我们自己定义的View上。

    用户每触摸一次屏幕都会运行一次这个操作。

    (2)因为每次运行invalidate()方法。都会触发onDraw方法。因此初始化的工作应通通放在自己定义View的构造方法中以节省内存,这个问题在《【Android】利用自己定义View的重绘实现拖动移动,获取组件的尺寸》(点击打开链接)已经讲过了,这里不再赘述。构造方法,完毕画家(画布)Canvas与画笔Paint,画图路径Path的初始化。

    关键是要把初始化之后的画纸cacheBitmap放到画家cacheCanvas手上,同一时候命令画家cacheCanvas把这张画纸cacheBitmap所有涂白,也就是说把绘图的背景颜色设置为白色。否则一会儿你保存出来的图像的背景色默认是黑色的。尽管你看到的自己定义View是白色的。

    在画笔Paint初始化的事情,注意要把画笔设置为paint.setStyle(Paint.Style.STROKE);不过画边的方法,这样才干做到涂鸦的效果,否则画笔默认是,附件绘图画矩形那种拖泥带水的效果。

    (3)触摸事件onTouchEvent里的作图方法这里反而没什么好说的。计算机图形学中最主要的内容。不懂就照复制就是了。

    (4)最后的保存图像的方法saveBitmap()也没什么好说的。

    就是安卓对sdcard卡的操作,详细见《【Android】读取sdcard上的图片》(点击打开链接),与Java对文件的操作的综合,详细见《【Java】输入与输出与JDK1.5之后的新型字符串StringBuilder》(点击打开链接)。

    5、通过自己定义的View,能让reslayoutactivity_main.xml这个MainActivity的布局xml。与MainActivity.java的代码变得简洁。

    reslayoutactivity_main.xml将变得例如以下的简短。就放一个DrawView,该实现的东西都在这个自己定义View中完毕。

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <com.painter.DrawView
            android:id="@+id/drawView1"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </FrameLayout>
    6、最后在MainActivity.java中实现指明各个菜单的实现方法就能完毕整个app,OnCreate方法根本就是什么都没有,不过载入布局文件。

    package com.painter;
    
    import android.os.Bundle;
    import android.app.Activity;
    import android.graphics.Color;
    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);
    	}
    
    	@Override
    	public boolean onCreateOptionsMenu(Menu menu) {
    		// Inflate the menu; this adds items to the action bar if it is present.
    		getMenuInflater().inflate(R.menu.main, menu);
    		return true;
    	}
    
    	// 处理菜单事件
    	@Override
    	public boolean onOptionsItemSelected(MenuItem item) {
    		DrawView drawView = (DrawView) findViewById(R.id.drawView1);
    		switch (item.getItemId()) {
    		// 设置id为menu_exit的菜单子项所要运行的方法。

    case R.id.menu1_sub1: drawView.paint.setStrokeWidth(1); break; case R.id.menu1_sub2: drawView.paint.setStrokeWidth(5); break; case R.id.menu1_sub3: drawView.paint.setStrokeWidth(10); break; case R.id.menu1_sub4: drawView.paint.setStrokeWidth(50); break; case R.id.menu2: drawView.paint.setColor(Color.BLACK); break; case R.id.menu3: drawView.paint.setColor(Color.WHITE); break; case R.id.menu4: try { drawView.saveBitmap(); } catch (Exception e) { e.printStackTrace(); } break; case R.id.menu5: System.exit(0);// 结束程序 break; } return true; } }

    最后。我上传了一份源代码给大家:http://download.csdn.net/detail/yongh701/8900457

  • 相关阅读:
    js原生图片拼图Demo
    display:inline-block在ie7下的解决办法
    Apollo 配置中心部署注意事项
    chrony 时间同步配置
    IPv6基础介绍
    Rabbitmq 报错 nodedown
    Maven 私服你应该不陌生吧,可你会用 Artifactory 搭建吗?
    你 MySQL 中重复数据多吗,教你一招优雅的处理掉它们!
    MySQL 数据库的基本使用
    自建 yum 源
  • 原文地址:https://www.cnblogs.com/yangykaifa/p/6888998.html
Copyright © 2011-2022 走看看