参考Android 自定义View (一)这篇博客,总结出自定义View的步骤:
1、自定义View的属性
2、在View的构造方法中获得自定义属性
[3、重写onMesure]
4、重写onDraw
再参照构建一个 Android 的简单绘图应用这篇翻译文章,以一个简单的画板例子走走整个流程。画板实现简单的随手涂鸦,双击清屏和按音量键分享的功能。
自义定View
自定义View的属性
先在res/values/ 下建立一个属性文件(attrs.xml), 在里面声明自定义属性。
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MyView"> <attr name="paintColor" format="color"/> <attr name="paintSize" format="dimension"/> </declare-styleable> </resources>
|
定义了画笔颜色和粗细。
获得自定义属性
在自定义View的构造方法中获得自定义属性。
1 2 3 4 5 6 7 8 9 10 11 12
| public MyView(Context context, AttributeSet attrs) { super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView); int paintColor = typedArray.getColor(R.styleable.MyView_paintColor, Color.BLACK); int paintSize = typedArray.getDimensionPixelSize(R.styleable.MyView_paintSize, 12); typedArray.recycle();
... }
|
重写onDraw
1 2 3
| protected void onDraw(Canvas canvas) { canvas.drawPath(path, paint); }
|
重写onTouchEvent事件
触碰屏幕和拖动手指的路径需要用onTouchEvent事件,并用Path对象封装路径,思路如下:
- 首先,在我们第一次触碰屏幕(
MotionEvent.ACTION_DOWN
)时创建一个路径。
- 这里需要一个双击判断,我们的思路是通过比较两次按下的时间间隔来判断
- 然后,当手指拖动时,我们不断增加点到路径里(
MotionEvent.ACTION_MOVE
)
- 最后,当手指停止时,我们也停止增加点并且invalidate视图强行重绘。
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| private long firstClick; private int countClick; @Override public boolean onTouchEvent(MotionEvent event) { float pointX = event.getX(); float pointY = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (firstClick != 0 && System.currentTimeMillis() - firstClick > 200) { countClick = 0; } countClick ++; if (countClick == 1) { firstClick = System.currentTimeMillis(); path.moveTo(pointX, pointY);
} else if (countClick == 2) { long lastClick = System.currentTimeMillis(); if (lastClick - firstClick < 200) { path.reset(); break; } } return true; case MotionEvent.ACTION_MOVE: path.lineTo(pointX, pointY); break; default: break; } postInvalidate(); return false; }
|
至此完成了自定义View的过程,接下来就是在程序中使用自定义View了。
使用View
在XML中布局
先在布局文件(res/layout/activity_main.xml)中声明我们的自定义View。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?xml version="1.0" encoding="utf-8"?> <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" tools:context="com.leon.myappdraw2.MainActivity" xmlns:custom="http://schemas.android.com/apk/res-auto"> <com.leon.myappdraw2.ui.MyView android:id="@+id/myView" android:layout_width="match_parent" android:layout_height="match_parent" custom:paintColor="#00ffff" custom:paintSize="12dp"/> </RelativeLayout>
|
对于自定义的paintColor和paintSize属性,需要引入 xmlns:custom=”http://schemas.android.com/apk/res-auto" 命名空间。
在Acitvity中使用
在Acitvity中使用,主要实现两个功能:
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| public class MainActivity extends AppCompatActivity {
private MyView myView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myView= (MyView) findViewById(R.id.myView); }
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: Bitmap bitmap = Bitmap.createBitmap(myView.getWidth(), myView.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); myView.draw(canvas); String image = saveBitmap(bitmap); if (image!=null) { shareImage(image); } else { Toast.makeText(MainActivity.this, "图片保存失败!", Toast.LENGTH_SHORT).show(); } return true; default: break; } return super.onKeyDown(keyCode, event); }
private void shareImage(String filename) { Uri imageUri = Uri.fromFile(new File(filename)); Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri); shareIntent.setType("image/*"); startActivity(Intent.createChooser(shareIntent, "分享到")); }
public String saveBitmap(Bitmap bitmap){
String albumPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + File.separator + getString(R.string.app_name); String imagePath = albumPath+File.separator+System.currentTimeMillis() + ".png";
File album = new File(albumPath); if (!album.isDirectory()&&!album.mkdirs()) { return null; }
FileOutputStream out; try { out = new FileOutputStream(imagePath); bitmap.compress(Bitmap.CompressFormat.PNG, 90, out); out.flush(); out.close(); return imagePath;
} catch (IOException e) { e.printStackTrace(); } return null; } }
|
在AndroidManifest.xml中设置权限
1 2
| <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
|
运行结果
注意点备忘
AppCompat设置全屏
在res/values/styles.xml中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style>
<style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style>
<style name="AppTheme.NoActionBar.Fullscreen"> <item name="android:windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item> </style>
</resources>
|
1 2 3
| <div align="center"> <img src="url" width = "270" height = "480" alt="图片名称" /> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12
| paint.setColor(paintColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(paintSize);
paint.setAntiAlias(true);
paint.setDither(true);
paint.setPathEffect(new CornerPathEffect(50));
|
getExternalStoragePublicDirectory(String type)的type类型
类型 |
说明 |
DIRECTORY_PICTURES |
图片存放的标准目录 |
DIRECTORY_DCIM |
相机拍摄照片和视频的标准目录 |
DIRECTORY_ALARMS |
系统提醒铃声存放的标准目录 |
DIRECTORY_DOWNLOADS |
下载的标准目录 |
DIRECTORY_MOVIES |
电影存放的标准目录 |
DIRECTORY_MUSIC |
音乐存放的标准目录 |
DIRECTORY_NOTIFICATIONS |
系统通知铃声存放的标准目录 |
DIRECTORY_PICTURES |
图片存放的标准目录 |
DIRECTORY_PODCASTS |
系统广播存放的标准目录 |
DIRECTORY_RINGTONES |
系统铃声存放的标准目录 |