最近研究了一下 svg 图片,可缩放矢量图片这个特性,也要在手写控件上支持。花费了不少精力,最终实现了这个功能。支持原笔迹,压感,笔锋,无失真缩放,这个特性,算是比较先进的了。后期对网络的支持更进一步。
看效果图(博客不支持显示 SVG,看一下谷歌浏览器的效果吧。):
Android 开发相关
最近研究了一下 svg 图片,可缩放矢量图片这个特性,也要在手写控件上支持。花费了不少精力,最终实现了这个功能。支持原笔迹,压感,笔锋,无失真缩放,这个特性,算是比较先进的了。后期对网络的支持更进一步。
看效果图(博客不支持显示 SVG,看一下谷歌浏览器的效果吧。):
最近的工作成果,主动电容屏原笔迹手写方案:
平台:RK3288 Android sdk + gt928 触摸屏IC
笔锋,流畅度,手和笔的区分都已经很成熟,而且效果也很好。
当然,配合我的悟空手写插件,实现的效果,商业化也是一流的方案。
效果图:
因为众所周知的技术原因,Android 相关的官方网站无法访问,造成 Android 开发相关的工具,官方文档都无法访问,特别是SDK,ADT无法更新。这里推荐一个新手老鸟都必备的网站: androiddevtools.cn
收集整理Android开发所需的Android SDK、开发中用到的工具、Android开发教程、Android设计规范,免费的设计素材等。
欢迎大家推荐自己在Android开发过程中用的好用的工具、学习开发教程、用到设计素材,欢迎Star、Fork 。
因项目需要把 Android Framework 自主开发部分的代码进行代码混淆,首先的工具是 PROGUARD。但是 PROGUARD 更多是用于 APK 的代码混淆。网络上还没有看到对 Framework 代码混淆的案列。
网上找不到方法,只好自己动手了。研究了两天,终于实现了 Framework 层的代码混淆。
代码放在 framework/base 目录下,编译生成 framework.jar,其实这不是一个很好的做法,因为与 Android 源生的代码混合在一起,造成低耦合。前期考虑不当了,后期维护就相对麻烦。
最关键的一点,修改 Android.mk, 使需要混淆部分的包和类单独编译。因为 framework 其他代码最好不要混淆,以免引起找不到类的出错提示。
然后设置 LOCAL_PROGUARD_ENABLED := nosystem,很重要,不然类的方法不会混淆成abcd类似的名称。
然后指定 proguard.flag,设定不混淆(保留)对外公开接口的类,重新编译。可以用模块编译 mmm 命令提高编译效率。
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
一直想在尝试两台设备之间互相连接,通信,交互的功能。了解到Socket,很有意思。基本可以实现我的设想,甚至可以多台设备互联,不清楚成熟的物联网又是通过什么什么进行互联?
我的初步目标是用 Android 手机控制小车,不过小车估计自己是做不来了,硬件能力都荒废了。
再熟悉一下 Socket 的工作机制:
手写控件相关文章:http://kevinems.com/tag/%E6%89%8B%E5%86%99
本人在好记星平板电脑 N818S 上开发的原笔迹手写控件,压感,笔锋都实现了,笔迹保存,还原,删除,放大,缩小,旋转,移动功能也实现了,控件的API简单易用,可以说是一个比较完善的控件了,并集成到 Framework,小有成就感啊。
当初需求统计,分析是就认定是一个相对复杂的控件,所以前期做了不少工作,后期编码实现的时候相对还算比较顺利。
这是第一次真正意义上遵循了标准的软件开发流程,开发效率果然提高不少。科学的开发方法,事半功倍。
流程大概:
平台:好记星 N818S,Android 4.2,汉王电磁屏,电磁笔
笔触效果:钢笔,铅笔,马克笔,毛笔
效果图:
一个终端培训师的作品~~~
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
序列化 (Serialization) 的相关知识可以参考相关的百科,这里只要是学习 android.graphics.Path 的序列化。
工作中有需求要保存绘制的 paths, 这个时候就需要用到对象的序列化的功能,但是 Android 上没有对 Path 做序列化的工作,所以只能自己进行序列化了。
Path 类序列化的原理就是通过将 path 绘制的每一个点都保存下来,需要还原的时候就进行反序列化,再通过这些保存的点重新绘制 path。
继承 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; } } }
创建 PathInfo 类,方法 lineStart, lineMove, lineEnd 专门用来保存 path 绘制的每一个点的坐标数据;反序列化的时候重绘 path。原理跟实例1差不多。
之前在 Android adb 网络连接 中总结了一次 adb 的网络连接,而最近工作中遇到 Android 设备连接 adb 的时候出现 offline 的提示,无法正常连接上设备,这个情况应该如何解决呢?
这里总结一下解决方案:
如果这个能帮到你,请留言告诉我。
理论学习了 Android Path 类之后,对 Path 类的属性方法都有所了解,写两个例子实践一下。理论与实践结合嘛。
实例1,五环 + 文字:
源码:
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 的手写板:
源码:
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; } }