原笔迹手写控件支持保存为 svg 图片

最近研究了一下 svg 图片,可缩放矢量图片这个特性,也要在手写控件上支持。花费了不少精力,最终实现了这个功能。支持原笔迹,压感,笔锋,无失真缩放,这个特性,算是比较先进的了。后期对网络的支持更进一步。

看效果图(博客不支持显示 SVG,看一下谷歌浏览器的效果吧。):
paintview_svg

 

Android开发必备站点:androiddevtools.cn

因为众所周知的技术原因,Android 相关的官方网站无法访问,造成 Android 开发相关的工具,官方文档都无法访问,特别是SDK,ADT无法更新。这里推荐一个新手老鸟都必备的网站: androiddevtools.cn

收集整理Android开发所需的Android SDK、开发中用到的工具、Android开发教程、Android设计规范,免费的设计素材等。

欢迎大家推荐自己在Android开发过程中用的好用的工具、学习开发教程、用到设计素材,欢迎Star、Fork 。

Android Framework 代码的混淆

因项目需要把 Android Framework 自主开发部分的代码进行代码混淆,首先的工具是 PROGUARD。但是 PROGUARD 更多是用于 APK 的代码混淆。网络上还没有看到对 Framework 代码混淆的案列。

网上找不到方法,只好自己动手了。研究了两天,终于实现了 Framework 层的代码混淆。

1.修改 Android.mk

代码放在 framework/base 目录下,编译生成 framework.jar,其实这不是一个很好的做法,因为与 Android 源生的代码混合在一起,造成低耦合。前期考虑不当了,后期维护就相对麻烦。

最关键的一点,修改 Android.mk, 使需要混淆部分的包和类单独编译。因为 framework 其他代码最好不要混淆,以免引起找不到类的出错提示。

然后设置 LOCAL_PROGUARD_ENABLED := nosystem,很重要,不然类的方法不会混淆成abcd类似的名称。

然后指定 proguard.flag,设定不混淆(保留)对外公开接口的类,重新编译。可以用模块编译 mmm 命令提高编译效率。

继续阅读Android Framework 代码的混淆

Socket 通信,小试牛刀

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

一直想在尝试两台设备之间互相连接,通信,交互的功能。了解到Socket,很有意思。基本可以实现我的设想,甚至可以多台设备互联,不清楚成熟的物联网又是通过什么什么进行互联?

我的初步目标是用 Android 手机控制小车,不过小车估计自己是做不来了,硬件能力都荒废了。

再熟悉一下 Socket 的工作机制:

socket

继续阅读Socket 通信,小试牛刀

Android 原笔迹手写控件,压感,笔锋不可少!

手写控件相关文章:http://kevinems.com/tag/%E6%89%8B%E5%86%99

本人在好记星平板电脑 N818S 上开发的原笔迹手写控件,压感,笔锋都实现了,笔迹保存,还原,删除,放大,缩小,旋转,移动功能也实现了,控件的API简单易用,可以说是一个比较完善的控件了,并集成到 Framework,小有成就感啊。

当初需求统计,分析是就认定是一个相对复杂的控件,所以前期做了不少工作,后期编码实现的时候相对还算比较顺利。

这是第一次真正意义上遵循了标准的软件开发流程,开发效率果然提高不少。科学的开发方法,事半功倍。

流程大概:

  1. 需求统计
  2. 需求分析
  3. 概要设计
  4. 详细设计
  5. 编码
  6. 测试
  7. 软件交付
  8. 软件维护

平台:好记星 N818S,Android 4.2,汉王电磁屏,电磁笔

笔触效果:钢笔,铅笔,马克笔,毛笔

效果图:

一个终端培训师的作品~~~

PaintView

welcome

继续阅读Android 原笔迹手写控件,压感,笔锋不可少!

Android AlarmManager 唤醒睡眠中的机器

AlarmManager 的使用是 Android 初学者比学掌握的,网上教程一箩筐,这里就不罗嗦了。

这里有一个比较好的教程 Android – Creating an Alarm with AlarmManager

本文的重点是如何把休眠的机器唤醒,点亮屏幕并显示相关的界面。

但是值得注意的就是,AlarmManager 唤醒系统后,跑完相关程序后,又会继续休眠下去。

如果你需要像我一样,唤醒屏幕去显示闹钟界面,那就要加上 WakeLock。

WakeLock 相关源码:

private PowerManager.WakeLock wl;
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "My Tag");
wl.acquire();
...
... //The rest of the onCreate() is still the same (for now)
}

protected void onStop() {
super.onStop();
wl.release();
}

// 值得注意的是,WakeLock 必须 release, 具体位置可以自己安排

// 别忘了加上权限
uses-permission android:name="android.permission.WAKE_LOCK";

更多可以参考这里: Starting Activity from Sleeping Device

android.graphics.Path 的序列化

序列化 (Serialization) 的相关知识可以参考相关的百科,这里只要是学习 android.graphics.Path 的序列化。

工作中有需求要保存绘制的 paths, 这个时候就需要用到对象的序列化的功能,但是 Android 上没有对 Path 做序列化的工作,所以只能自己进行序列化了。

Path 类序列化的原理就是通过将 path 绘制的每一个点都保存下来,需要还原的时候就进行反序列化,再通过这些保存的点重新绘制 path。

实例1:

继承 android.graphics.Path 类,重写 moveTo, lineTo 等方法,记录下 path 绘制的每一点的坐标数据;反序列化的时候调用 “drawThisPath” 重绘 path。

相关代码:

public class CustomPath extends Path implements Serializable {

private static final long serialVersionUID = -5974912367682897467L;

private ArrayList<PathAction> actions = new ArrayList<CustomPath.PathAction>();

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
    in.defaultReadObject();
    drawThisPath();
}

@Override
public void moveTo(float x, float y) {
    actions.add(new ActionMove(x, y));
    super.moveTo(x, y);
}

@Override
public void lineTo(float x, float y){
    actions.add(new ActionLine(x, y));
    super.lineTo(x, y);
}

private void drawThisPath(){
    for(PathAction p : actions){
        if(p.getType().equals(PathActionType.MOVE_TO)){
            super.moveTo(p.getX(), p.getY());
        } else if(p.getType().equals(PathActionType.LINE_TO)){
            super.lineTo(p.getX(), p.getY());
        }
    }
}

public interface PathAction {
    public enum PathActionType {LINE_TO,MOVE_TO};
    public PathActionType getType();
    public float getX();
    public float getY();
}

public class ActionMove implements PathAction, Serializable{
    private static final long serialVersionUID = -7198142191254133295L;

    private float x,y;

    public ActionMove(float x, float y){
        this.x = x;
        this.y = y;
    }

    @Override
    public PathActionType getType() {
        return PathActionType.MOVE_TO;
    }

    @Override
    public float getX() {
        return x;
    }

    @Override
    public float getY() {
        return y;
    }

}

public class ActionLine implements PathAction, Serializable{
    private static final long serialVersionUID = 8307137961494172589L;

    private float x,y;

    public ActionLine(float x, float y){
        this.x = x;
        this.y = y;
    }

    @Override
    public PathActionType getType() {
        return PathActionType.LINE_TO;
    }

    @Override
    public float getX() {
        return x;
    }

    @Override
    public float getY() {
        return y;
    }

}
}

实例2:

创建 PathInfo 类,方法 lineStart, lineMove, lineEnd 专门用来保存 path 绘制的每一个点的坐标数据;反序列化的时候重绘 path。原理跟实例1差不多。

源码下载

Android adb offline 的解决方案

之前在 Android adb 网络连接 中总结了一次 adb 的网络连接,而最近工作中遇到 Android 设备连接 adb 的时候出现 offline 的提示,无法正常连接上设备,这个情况应该如何解决呢?

这里总结一下解决方案:

  1. 重新插拔 USB 线
  2. 换一个 USB 口,有些 PC 的 USB 口不都是一样的;或者换一根 USB 线,低质量的 USB 线缆可能会引起这个问题
  3. 取消 adb 调试模式,再重新勾选
  4. 在 eclipse 的 DDMS 模式下点击 “reset adb”
  5. 在 PC 端重启 adb。adb kill-server 然后 adb start-server
  6. 重启 PC手机,不行的话再试几次
  7. 升级 android SDK,对于开发者来说,一般都是系统与 adb 工具版本不一致引起的。例如我的 Nexus 7 升级到 Android 4.3之后,只能通过升级 Android SDK 来解决这个问题
  8. 将xx手机助手之类的相关进程杀掉就正常了(感谢“梁c”的补充)。xx助手之类的也会将 adb 进程锁定,导致无法正常连接 adb.

如果这个能帮到你,请留言告诉我。

Android Path 类运用实践

理论学习了 Android Path 类之后,对 Path 类的属性方法都有所了解,写两个例子实践一下。理论与实践结合嘛。

实例1,五环 + 文字:

useesee

源码:

package com.example.pathstudy;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Bundle;
import android.view.View;

public class PathStudyActivity extends Activity {

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(new PathStudyView(getApplicationContext()));
	}

	private class PathStudyView extends View {

		public PathStudyView(Context context) {
			super(context);
			// TODO Auto-generated constructor stub
		}

		@Override
		protected void onDraw(Canvas canvas) {
			// TODO Auto-generated method stub
			// super.onDraw(canvas);
			canvas.drawColor(Color.WHITE);
			Paint mPaint = new Paint();
			mPaint.setStyle(Paint.Style.STROKE);
			mPaint.setStrokeWidth(20);

			int originX = 0;
			int originY = 0;
			int offsetX = 0;
			int offsetY = 0;
			int offsetLengthX = 240;
			int offsetLengthY = 110;

			float circleX = 150;
			float circleY = 200;
			float radius = 100;

			// first circle
			mPaint.setColor(Color.BLUE);
			Path mPath = new Path();
			mPath.addPath(getDrawCirclePath(circleX, circleY, radius));
			canvas.drawPath(mPath, mPaint);

			// second circle
			mPaint.setColor(Color.BLACK);
			canvas.save();
			offsetX = originX + offsetLengthX;
			canvas.translate(offsetX, offsetY);
			mPath.reset();
			mPath.addPath(getDrawCirclePath(circleX, circleY, radius));
			canvas.drawPath(mPath, mPaint);
			canvas.restore();

			// thid circle
			mPaint.setColor(Color.RED);
			canvas.save();
			offsetX += offsetLengthX;
			canvas.translate(offsetX, offsetY);
			mPath.reset();
			mPath.addPath(getDrawCirclePath(circleX, circleY, radius));
			canvas.drawPath(mPath, mPaint);
			canvas.restore();

			// forth circle
			mPaint.setColor(Color.YELLOW);
			canvas.save();
			offsetX = originX + offsetLengthX / 2;
			offsetY = originY + offsetLengthY;
			canvas.translate(offsetX, offsetY);
			mPath.reset();
			mPath.addPath(getDrawCirclePath(circleX, circleY, radius));
			canvas.drawPath(mPath, mPaint);
			canvas.restore();

			// fifth circle
			mPaint.setColor(Color.GREEN);
			canvas.save();
			offsetX += offsetLengthX;
			canvas.translate(offsetX, offsetY);
			mPath.reset();
			mPath.addPath(getDrawCirclePath(circleX, circleY, radius));
			canvas.drawPath(mPath, mPaint);
			canvas.restore();

			// draw text kevinems.com
			// fifth circle
			mPaint.setColor(Color.BLACK);
			mPaint.setStyle(Paint.Style.FILL);
			mPaint.setTextSize(100);
			mPaint.setStrokeWidth(5);
			String websiteStr = "kevinems.com";
			canvas.drawText(websiteStr, 110, 500, mPaint);
			canvas.restore();

		}

		private Path getDrawCirclePath(float x, float y, float radius) {
			Path mPath = new Path();
			mPath.setFillType(Path.FillType.EVEN_ODD);
			mPath.addCircle(x, y, radius, Path.Direction.CCW);
			return mPath;
		}
	}
}

实例2,可以随意绘画 path 的手写板:

paint-pad

源码:

package com.example.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class PathView extends View {
	private static final String LOG_TAG = "PathView";
	private Path mPath;
	private Paint mPaint;
	private float mPosX;
	private float mPosY;

	public PathView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		init();
	}

	private void init() {
		// TODO Auto-generated method stub
		mPath = new Path();
		mPaint = new Paint();

		mPaint.setColor(Color.GREEN);
		mPaint.setStyle(Paint.Style.STROKE);
		mPaint.setStrokeWidth(5);

	}

	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		// super.onDraw(canvas);
		Log.i(LOG_TAG, "onDraw");
		canvas.drawColor(Color.WHITE);
		canvas.drawPath(mPath, mPaint);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		// return super.onTouchEvent(event);
		int action = event.getAction();
		float x = event.getX();
		float y = event.getY();

		switch (action) {
		case MotionEvent.ACTION_DOWN:
			mPath.moveTo(x, y);
			break;

		case MotionEvent.ACTION_MOVE:
			mPath.quadTo(mPosX, mPosY, x, y);

			invalidate();
			break;

		default:
			break;
		}

		mPosX = x;
		mPosY = y;

		return true;
	}
}