달력

4

« 2024/4 »

  • 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
728x90
반응형

[android] 터치로 그리기

안녕하세요?
프쟁이 입니다.

이 샘플소스는 안드로이드 설치폴더에 android-sdk-windows/samples/android-8/ApiDemos 위치에 원본소스가 있습니다.

shape 를 xml 파일에서 정의하여 가져오지 않고, 코드에서 동적으로 생성하여,
화면에 출력을 하고 있습니다. 커스텀 뷰를 정의하여, 이 객체를 액티비티에서
레이아웃을 구성할때 그 객체를 넘겨, 액티비티 화면에 출력을 하고 있습니다.
ShapeDrawable 객체들을 만들어서, 각 다양한 설정을 하여 Shape 를 생성하여
출력하고 있습니다.

먼저 액티비티 클래스인 FingerPaint.java 파일을 원하시는 패키지 경로에 추가해줍니다.
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package korsoft.net.Test008;
import android.app.Activity;
import android.content.Context;
import android.graphics.*;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
/*
* 액티비티에서 터치를 하여 드래그를 하면,그 경로대로 그리기가 수행이 됩니다.
* 스마트폰의 메뉴버튼을 누르면 다섯가지 메뉴가 뜨는데요, Color 메뉴를 선택하면,
* 색상선택 다이얼로그가 떠서 그리기 색상을 선택할 수 있습니다.
* 그러면 터치해서 드래그 할때 해당 색상으로 그리기가 수행됩니다.
* 그외 Emboss, Blur, Erase, SrcATop 메뉴들을 선택하면,
* 해당 효과들이 적용된 선들을 그릴 수 있습니다.
*/
public class FingerPaint extends Activity
implements ColorPickerDialog.OnColorChangedListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFFFF0000);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(12);

mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 },
0.4f, 6, 3.5f);
mBlur = new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL);
}

private Paint mPaint;
private MaskFilter mEmboss;
private MaskFilter mBlur;

/*
* (non-Javadoc)
* @see korsoft.net.Test008.ColorPickerDialog.OnColorChangedListener#colorChanged(int)
* 색상선택 다이얼로그에서 색상을 선택했을때 이 메소드가 호출되고,
* 선택한 색상값이 color 변수에 넘어옵니다. 이 값을 그리기 객체에
* 색상값을 설정해줍니다. 다이얼로그에서 색상값을 변경하게 되면,
* 터치로 그리기를 수행할때, 바뀐 색상값으로 적용되게 됩니다.
*/
public void colorChanged(int color) {
mPaint.setColor(color);
}
/*
* 사용자 정의 커스텀 뷰 클래스입니다.
*/
public class MyView extends View {

private static final float MINP = 0.25f;
private static final float MAXP = 0.75f;

private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
private Paint mBitmapPaint;

public MyView(Context c) {
super(c);

/*
* 비어있는 비트맵을 생성합니다.
* 이 비트맵에 그리기를 수행하게 됩니다.
*/
mBitmap = Bitmap.createBitmap(320, 480, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}

/*
* (non-Javadoc)
* @see android.view.View#onDraw(android.graphics.Canvas)
* 액티비티에 그리기를 수행할때 호출되는 메소드입니다.
* 먼저 이전에 그렸던 내용이 비트맵에 저장되어있으므로,
* 먼저 비트맵을 출력하고, 현재 Path 를 출력하여,
* 현재 터치 드래그 해서, 선을 그리게 됩니다.
*/
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFAAAAAA);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);

canvas.drawPath(mPath, mPaint);
}

private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;

/*
* 액티비티 화면에 터치를 하면 선을 초기화 합니다.
* 현재 경로값을 업데이트 합니다.
* 실제 터치 이벤트는 밑에 onTouchEvent 메소드에서
* 발생하고, 그 메소드에서 이 메소드를 호출합니다.
*/
private void touch_start(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
/*
* 터치 후 드래그를 하면 계속해서 이 메소드가 실시간으로
* 호출되고, 현재 패스값을 바꾸면서 저장을 합니다.
* 현재 경로값을 실시간으로 업데이트 합니다.
* 실제 터치 이벤트는 밑에 onTouchEvent 메소드에서
* 발생하고, 그 메소드에서 이 메소드를 호출합니다.
*/
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
mX = x;
mY = y;
}
}
/*
* 드래그하다가 손을 화면에서 떼는 순간 호출되어,
* 선을 그리기를 마무리 합니다.
* 실제 터치 이벤트는 밑에 onTouchEvent 메소드에서
* 발생하고, 그 메소드에서 이 메소드를 호출합니다.
*/
private void touch_up() {
mPath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mPath, mPaint);
// kill this so we don't double draw
mPath.reset();
}

/*
* (non-Javadoc)
* @see android.view.View#onTouchEvent(android.view.MotionEvent)
* 위의 세가지 터치 이벤트에서 설정한 경로값을 실시간으로,
* 이 이벤트에서 각 터치 이벤트 종류에 따라서 경로값을 재설정하고,
* invalidate 메소드를 호출하여, 액티비티 화면에 다시 그리기를 수행합니다.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
}

private static final int COLOR_MENU_ID = Menu.FIRST;
private static final int EMBOSS_MENU_ID = Menu.FIRST + 1;
private static final int BLUR_MENU_ID = Menu.FIRST + 2;
private static final int ERASE_MENU_ID = Menu.FIRST + 3;
private static final int SRCATOP_MENU_ID = Menu.FIRST + 4;
/*
* (non-Javadoc)
* @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
* 다섯개의 메뉴를 나오게 합니다.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);

menu.add(0, COLOR_MENU_ID, 0, "Color").setShortcut('3', 'c');
menu.add(0, EMBOSS_MENU_ID, 0, "Emboss").setShortcut('4', 's');
menu.add(0, BLUR_MENU_ID, 0, "Blur").setShortcut('5', 'z');
menu.add(0, ERASE_MENU_ID, 0, "Erase").setShortcut('5', 'z');
menu.add(0, SRCATOP_MENU_ID, 0, "SrcATop").setShortcut('5', 'z');
/**** Is this the mechanism to extend with filter effects?
Intent intent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(
Menu.ALTERNATIVE, 0,
new ComponentName(this, NotesList.class),
null, intent, 0, null);
*****/
return true;
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
return true;
}

/*
* (non-Javadoc)
* @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
* 다섯개의 각 메뉴별로 처리를 합니다.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
mPaint.setXfermode(null);
mPaint.setAlpha(0xFF);
switch (item.getItemId()) {
case COLOR_MENU_ID:
new ColorPickerDialog(this, this, mPaint.getColor()).show();
return true;
case EMBOSS_MENU_ID:
if (mPaint.getMaskFilter() != mEmboss) {
mPaint.setMaskFilter(mEmboss);
} else {
mPaint.setMaskFilter(null);
}
return true;
case BLUR_MENU_ID:
if (mPaint.getMaskFilter() != mBlur) {
mPaint.setMaskFilter(mBlur);
} else {
mPaint.setMaskFilter(null);
}
return true;
case ERASE_MENU_ID:
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
return true;
case SRCATOP_MENU_ID:
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.SRC_ATOP));
mPaint.setAlpha(0x80);
return true;
}
return super.onOptionsItemSelected(item);
}
}
액티비티에서 Color 메뉴를 선택했을때 뜨는 색상선택 다이얼로그를 정의하는 ColorPickerDialog.java 파일을 해당 패키지경로에 추가해줍니다.

/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package korsoft.net.Test008;
import android.os.Bundle;
import android.app.Dialog;
import android.content.Context;
import android.graphics.*;
import android.view.MotionEvent;
import android.view.View;
/*
* 색상을 선택할 수 있는 커스텀 다이얼로그 입니다.
* 주변 원테두리에 선택할 수 있는 색상들을 배치하여,
* 중앙 원에 현재 선택한 색상을 표시합니다.
* 이 다이얼로그 클래스의 OnColorChangedListener 인터페이스를
* 구현하여, 중앙 원의 현재색상을 선택했을때, 이 인터페이스를
* 구현한 액티비티등에서 색상값을 가져와서 원하는 처리를 하시면 됩니다.
*/
public class ColorPickerDialog extends Dialog {
/*
* 이 인터페이스를 구현할 액티비티 등에서 색상값을 선택했을때
* 그 색상값을 반환해 주게 됩니다.
*/
public interface OnColorChangedListener {
void colorChanged(int color);
}
private OnColorChangedListener mListener;
private int mInitialColor;
/*
* 다이얼로그에 표시할 커스텀 뷰 클래스 입니다.
*/
private static class ColorPickerView extends View {
private Paint mPaint;
private Paint mCenterPaint;
private final int[] mColors;
private OnColorChangedListener mListener;

/*
* 커스텀 뷰 초기화 내용입니다.
*/
ColorPickerView(Context c, OnColorChangedListener l, int color) {
super(c);
mListener = l;
mColors = new int[] {
0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00,
0xFFFFFF00, 0xFFFF0000
};
Shader s = new SweepGradient(0, 0, mColors, null);

//선택할 색상값을 표시할 테두리 원을 그릴 그리기 객체입니다.
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setShader(s);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(32); //선두께를 32픽셀로 주었습니다.

//중앙에 현재 색상을 표시할 원을 그릴 그리기 객체입니다.
mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCenterPaint.setColor(color);
mCenterPaint.setStrokeWidth(5);
}

private boolean mTrackingCenter;
private boolean mHighlightCenter;
/*
* (non-Javadoc)
* @see android.view.View#onDraw(android.graphics.Canvas)
* 다이얼로그화면에 그리기시 호출되는 메소드입니다.
* 테두리 색상선택 원과 중앙의 현재 선택색상 원을 그립니다.
*/
@Override
protected void onDraw(Canvas canvas) {
float r = CENTER_X - mPaint.getStrokeWidth()*0.5f;

//원점을 화면의 중앙점으로 잡습니다.
canvas.translate(CENTER_X, CENTER_X);

canvas.drawOval(new RectF(-r, -r, r, r), mPaint);
canvas.drawCircle(0, 0, CENTER_RADIUS, mCenterPaint);

if (mTrackingCenter) {
int c = mCenterPaint.getColor();
mCenterPaint.setStyle(Paint.Style.STROKE);

if (mHighlightCenter) {
mCenterPaint.setAlpha(0xFF);
} else {
mCenterPaint.setAlpha(0x80);
}
canvas.drawCircle(0, 0,
CENTER_RADIUS + mCenterPaint.getStrokeWidth(),
mCenterPaint);

mCenterPaint.setStyle(Paint.Style.FILL);
mCenterPaint.setColor(c);
}
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(CENTER_X*2, CENTER_Y*2);
}

private static final int CENTER_X = 100;
private static final int CENTER_Y = 100;
private static final int CENTER_RADIUS = 32;

private int floatToByte(float x) {
int n = java.lang.Math.round(x);
return n;
}
private int pinToByte(int n) {
if (n < 0) {
n = 0;
} else if (n > 255) {
n = 255;
}
return n;
}

private int ave(int s, int d, float p) {
return s + java.lang.Math.round(p * (d - s));
}

private int interpColor(int colors[], float unit) {
if (unit <= 0) {
return colors[0];
}
if (unit >= 1) {
return colors[colors.length - 1];
}

float p = unit * (colors.length - 1);
int i = (int)p;
p -= i;
// now p is just the fractional part [0...1) and i is the index
int c0 = colors[i];
int c1 = colors[i+1];
int a = ave(Color.alpha(c0), Color.alpha(c1), p);
int r = ave(Color.red(c0), Color.red(c1), p);
int g = ave(Color.green(c0), Color.green(c1), p);
int b = ave(Color.blue(c0), Color.blue(c1), p);

return Color.argb(a, r, g, b);
}

private int rotateColor(int color, float rad) {
float deg = rad * 180 / 3.1415927f;
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);

ColorMatrix cm = new ColorMatrix();
ColorMatrix tmp = new ColorMatrix();
cm.setRGB2YUV();
tmp.setRotate(0, deg);
cm.postConcat(tmp);
tmp.setYUV2RGB();
cm.postConcat(tmp);

final float[] a = cm.getArray();
int ir = floatToByte(a[0] * r + a[1] * g + a[2] * b);
int ig = floatToByte(a[5] * r + a[6] * g + a[7] * b);
int ib = floatToByte(a[10] * r + a[11] * g + a[12] * b);

return Color.argb(Color.alpha(color), pinToByte(ir),
pinToByte(ig), pinToByte(ib));
}

private static final float PI = 3.1415926f;
/*
* (non-Javadoc)
* @see android.view.View#onTouchEvent(android.view.MotionEvent)
* 다이얼로그를 터치했을때, 주변 테두리의 색상선택영역을 터치했는지,
* 아니면 중앙의 현재색상을 터치했는지에 따라서 해당 처리를 하고있습니다.
* 테두리의 색상을 선택했을면, 중앙의 원의 색상이 해당 색상으로 바뀌고,
* 중앙의 현재 선택한 색상원을 터치하면, 다이얼로그가 닫히면서,
* 현재 선택된 색상값을 반환하게 됩니다.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX() - CENTER_X;
float y = event.getY() - CENTER_Y;
boolean inCenter = java.lang.Math.sqrt(x*x + y*y) <= CENTER_RADIUS;

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mTrackingCenter = inCenter;
if (inCenter) {
mHighlightCenter = true;
invalidate();
break;
}
case MotionEvent.ACTION_MOVE:
if (mTrackingCenter) {
if (mHighlightCenter != inCenter) {
mHighlightCenter = inCenter;
invalidate();
}
} else {
float angle = (float)java.lang.Math.atan2(y, x);
// need to turn angle [-PI ... PI] into unit [0....1]
float unit = angle/(2*PI);
if (unit < 0) {
unit += 1;
}
mCenterPaint.setColor(interpColor(mColors, unit));
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if (mTrackingCenter) {
if (inCenter) {
//현재 색상값 반환..
mListener.colorChanged(mCenterPaint.getColor());
}
mTrackingCenter = false; // so we draw w/o halo
invalidate();
}
break;
}
return true;
}
}
public ColorPickerDialog(Context context,
OnColorChangedListener listener,
int initialColor) {
super(context);

mListener = listener;
mInitialColor = initialColor;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OnColorChangedListener l = new OnColorChangedListener() {
public void colorChanged(int color) {
mListener.colorChanged(color);
dismiss();
}
};
setContentView(new ColorPickerView(getContext(), l, mInitialColor));
setTitle("Pick a Color");
}
}

AndroidManifest.xml 파일을 설정해줍니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="korsoft.net.Test008"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".FingerPaint" android:label="Graphics/FingerPaint">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

그럼 모두들 즐프하세요 ^^
728x90
반응형
:
Posted by mapagilove