總網頁瀏覽量

2013年4月20日 星期六

簽名、手記製作

程式的目的:
將手機螢幕當作便條紙可以在上面畫圖。


一、畫圖實作


圖構成的邏輯:
畫圖由多條線構成一張圖
而兩個以上的點構成一條線

實作:
1.宣告LinkedList 裡面存放多條線(多個HashMap物件)
2.宣告HashMap代表一個線條,HashMap存放線條的顏色、粗細,以及構成線條的座標
3.HashMap裡面其中一欄位存放LinkedList存放多個座標,每一個座標以HashMap姿態放進LinkedList中
(一條線由多個座標連起來所構成)
因此實作LinkedList為容器存放多個座標,

接著利用GestureDetector以及onTouchEvent onDraw onDown來實作將HashMap放進LinkedList
或者取出的動作啦


ScrollView:ScrollView也是layout一種,只限定vertical的方向。當scrollview裡面的元件超過螢幕顯示範圍,可以滾動方式取得元件。不可以放在LinearLayout之內。

自訂View:新增.java類別,類別必須繼承(extends View)。
且必須有建構式,本例子的建構式接收Context,AttributeSet

所謂Context就是指向parent對象的指標(或參考),受到parent控制
通常Context指向Activity
Context可以理解成程式(物件)所在的環境(狀態)
Context裡面存放Activity各式各樣的資訊


首先,準備好painel view
public class MainPanel extends View


在建構式宣告畫筆Paint
以及手勢偵測物件,告知gesturedetector所在context以及listener

在建構式外面宣告傾聽者類別繼承手勢傾聽者
new GestureDetector(context,MyListenter)
private void MyListener extends SimpleOnGestureListener{}


GestureListener要override OnDown 以及onScroll method
MainPanel(View) 要override onTouchEvent 以及onDraw method


範例程式:


public class MainPanel extends View {
private Paint p;// 畫筆
private GestureDetector gd;
private Context context;
private HashMap<String, Object> line;//
private LinkedList<HashMap<String, Object>> lines, redos;
private int size;// 筆畫粗細
private boolean isInit;
private boolean isDrawing;

public MainPanel(Context c, AttributeSet attrs) {
super(c, attrs);
context = c;// 從mainActivity來的?

//Log.i("context",context.toString());
//context指的是MainActivity@416a87d0

size = 10;
p = new Paint();
p.setColor(Color.MAGENTA);
p.setStrokeWidth(size);

gd = new GestureDetector(context, new MyListener());
// 手勢偵測物件,偵測碰觸螢幕的事件

setBackgroundColor(Color.TRANSPARENT);// View的顏色與parent一樣

}

private class MyListener extends SimpleOnGestureListener {

@Override
public boolean onDown(MotionEvent e) {
//onTouchEvent return gd.onTouchEvent(event)且action_down 會觸發這裡
//Log.i("Sing","onDown");

//onDown表示線條起點

float x = e.getX(), y = e.getY();
//手指碰到的座標

line.put("color", p.getColor());
line.put("size", (int)p.getStrokeWidth());

LinkedList<HashMap<String,Float>> lxy = new LinkedList();
HashMap<String,Float> xy = new HashMap();
xy.put("x", x);
xy.put("y", y);

lxy.add(xy);//HashMap放進Linklist
//將起始座標放入lxy[0]

line.put("line", lxy);//起始座標
// return super.onDown(e);
return true;
//retrun true會持續觸發touchevent 以及onScroll
}

// @Override
// public boolean onScroll(MotionEvent e1, MotionEvent e2,
// float distanceX, float distanceY) {
// Log.i("Sing","onScroll");
//
// return super.onScroll(e1, e2, distanceX, distanceY);
// }

}//GuestureDetector

//初始化
private void init(){
lines = new LinkedList<HashMap<String,Object>>();
redos= new LinkedList<HashMap<String,Object>>();

isDrawing = false;
isInit =true;

}


@Override
protected void onDraw(Canvas canvas) {
Log.i("Sing","onDraw");
super.onDraw(canvas);

if(!isInit) init();
//初始化,因為onDraw相當於View的main,
//但是有時會自動執行多次 所以就用init()初始化


//第一次進入這裡不會在View上畫出東西
//第一次進入畫面不會跑進foreachloop
for(HashMap<String,Object> line : lines){

// p.setColor(Color.BLACK);
// p.setStrokeWidth(3);
//所以這邊可以用條件判斷,當line裡面lxy的index符合某條件時
//更改線條顏色

p.setColor((Integer)line.get("color"));
//Log.i("Conan"," "+(Integer)line.get("color"));
p.setStrokeWidth((Integer)line.get("size"));
// Log.i("Conan"," "+(Integer)line.get("size"));

LinkedList<HashMap<String,Float>> lxy = (LinkedList)line.get("line");
//第一次treace程式碼:line存放兩個座標了

for(int i=1; i<lxy.size();i++){
HashMap<String,Float> xy0 = lxy.get(i-1);
//取得linkedlist 裡面物件 ,節點1
HashMap<String,Float> xy1 = lxy.get(i);
//節點2
canvas.drawLine(xy0.get("x"), xy0.get("y"), xy1.get("x"), xy1.get("y"), p);
// 畫線

}


}

}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Log.i("Sing","onTouchEvent");

if(!isDrawing){

//之前尚未開始畫圖,進入這裡進行初始化
//第二次treace:新增第二條線
line = new HashMap();
lines.add(line);//line是一條線,lines放多條線的LinkedList
//第二次trace到這,在lines[1]加上line HashMap物件
//還沒設定線條顏色與粗細
// Log.i("YYP", "" + lines.size());

//lines放n條線
isDrawing = true;
return gd.onTouchEvent(event);//第一次onTouchEvent後跳去onDown
}
else if(isDrawing && event.getAction()==MotionEvent.ACTION_UP)
{//正在畫圖  手指離開螢幕
isDrawing = false ;
return false;
}
else
{//isDrawing = true  event.getAction()==MotionEvent.Action_Move
//正在畫圖
LinkedList<HashMap<String,Float>> lxy = (LinkedList)line.get("line");
//這裡的line跟onDwon的line是同一個
//第二次trace到這邊:line裡面已經有兩個座標,
HashMap<String,Float> xy = new HashMap();
xy.put("x", event.getX());//線的終點
xy.put("y", event.getY());
//第二次trace到這邊:增加第三座標
lxy.add(xy);
//第一次trace將座標放入lxy[1]
//第二次trace到這 ,座標放入lxy[2]
postInvalidate();//呼叫ondraw
return gd.onTouchEvent(event);

}

// String str=event.toString();
// Log.i("Sing",str);
// int action = event.getAction();
//    cordinateX = (int) event.getX();
//    cordinateY = (int) event.getY();
//    switch (action) {
//    // 按下那一刻的動作
//    case MotionEvent.ACTION_DOWN:
// Log.v("Sing", "ACTION_DOWN");
// break;
//    // 移動的動作
//    case MotionEvent.ACTION_MOVE:
// Log.v("Sing", "ACTION_MOVE");
// break;
//    // 抬起的動作
//    case MotionEvent.ACTION_UP:
// Log.v("Sing", "ACTION_UP");
//// postInvalidate();//好像開啟另一條thread呼叫onDraw吧?
// break;  
//    }
//    return super.onTouchEvent(event);
//    return true;
}
}




二、開新檔案的功能:


以上完成之後,手指在螢幕上畫圖表示
將線條座標一一塞入LinkedList裡面。
開新檔案就是將LinkedList裡面的物件(線條、座標)清空
最快的方法重新new一個LinkedList物件,讓line指向新的物件
這樣就達到開新檔案了。



在MainPanel裡面宣告
void doInit(){
isInit=false;
postInvalidate;
//執行上面的onDraw
}

在MainActivity
宣告view變數
private MainPanel mp;
mp= (MainPanel)findViewById(R.id.mp);
//若沒寫這行會出現nullPointException

將mp.doInit();寫在btnOnClick事件裡。


三、復原、重做

以一個線條為單位復原重作。



void undo() {
//復原表示將LinkedList的元素逐一刪掉
if (!lines.isEmpty()) {
//lines.removeLast();
//
// lines裡面將線條移除
redos.add(lines.removeLast());
              //刪掉的元素放進redo
postInvalidate();
}
}
void redo(){

if((redos!=null)&&(redos.size()>0)){

lines.add(redos.removeLast());
//將redo的東西重新放入lines
postInvalidate();
}
}


四、自訂線條顏色(color picker實作)

取用別人寫好的code當作自己的函式庫libray
引用別人寫好的函式庫

1.
首先下載
android color picker
整個project程式碼

2. import android-color-picker

3.import之後在android-color-picker專案點右鍵,選properties
之後選擇"android"     之後將"is Libray"打勾   之後點apply並關閉視窗


4.回到原本project
在原本project點右鍵跟上面一樣選properties 選android
之後點add
將android-color-picker加入
這樣一來可以將自己/別人寫的code當作函式庫引用了

private void colorPicker(){
// initialColor is the initially-selected color to be shown in the rectangle on the left of the arrow.
// for example, 0xff000000 is black, 0xff0000ff is blue. Please be aware of the initial 0xff which is the alpha.
AmbilWarnaDialog dialog = new AmbilWarnaDialog(this,mp.getColor, new OnAmbilWarnaListener() {
        @Override
        public void onOk(AmbilWarnaDialog dialog, int color) {
                // color is the color selected by the user.
mp.setColor(color);        
}
                
        @Override
        public void onCancel(AmbilWarnaDialog dialog) {
                // cancel was selected by the user
        }
});
}

相關步驟以及整個code下載來源如下:
https://code.google.com/p/android-color-picker/





五、儲存檔案


首先去mainifest.xml設定權限
android.permission.WRITE_EXTERNAL_STORAGE
允許程式可以將資料儲存到手機上面去

接著利用MainPanel extends View物件的setDrawingCacheEnabled(true)

mp.setDrawingCacheEnabled(true);
讓onDraw的資訊可以快取在記憶體
不寫這行,下面程式會出現NotFoundException之類的字眼
Bitmap物件在記憶體中抓取不到資料


private void save(){}
FileOutputStream fout;
try{
fout = new FileOutputStream("/mnt/sdcard/name.png");
//指定輸出到內部儲存空間

Bitmap bmp = mp.getDrawingCache();
//Bitmap物件 接收快取記憶體裡的資料
bmp.compress(Compress.PNG,100,fout);
//輸出格式  輸出品質 輸出目的
fout.flush();
fout.close();


Toast.makeText(this, "saveOK", Toast.LENGTH_SHORT).show();

}catch(Exception e){
Log.i("Exception",e.toString());


}


六、自訂線條大小


SeekBar實作

private SeekBar seekbar;
seekbar = (SeekBar)findViewById(R.id.seekbar);
seekbar.setProgress(mp.getSize());
//設定seekbar初始值
seekbar.setMax(10);

//設定seekbar最大值

 seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener(){

@Override
public void onProgressChanged(SeekBar arg0, int progress, boolean fromUser) {
//progress:seekbar的值
mp.setSize(progress);
}

@Override
public void onStartTrackingTouch(SeekBar arg0) {

}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {

}});





int getSize(){

return (int)p.getStrokeWidth();

}

void setSize(int size){
p.setStrokeWidth(size);


}



七、自訂歡迎畫面



Application Node新增Activity
在Activity底下新增
intent filter將其他activity的intent filter刪掉
這樣可以依照自己意思指定起始畫面了



PS:



private Resource res;
res = getResources();//從Context取得Resource物件
//Resource物件可以在.java檔案取得該專案的xml資源設定值



Toast.makeText(this, res.getString(R.string.btn_save), Toast.LENGTH_SHORT).show();
//re.getString(R.string.btn_save);  印出string.xml中所對應的字串
去看values/string.xml有這樣標籤
<string name="btn_save">Save</string>







沒有留言:

張貼留言