Читайте также: |
|
За головне меню в програмі відповідає клас MainMenu (лістинг 3.3.1).
public class MainMenu extends Dialog {
Activity context = null;
Dialog menuDialog = this;
Draw draw = null;
ScrollView menuView = null;
int DPI = 0;
int menuBackgroundColor = Color. rgb (39, 50, 56);
int menuTextColor = Color. rgb (255,255,255);
ColorDrawable menuBackgroundDrawable = new ColorDrawable(menuBackgroundColor);
int buttonColorActive = Color. argb (255, 60, 100, 200);
int buttonColorPassive = Color. argb (255, 60, 60, 60);
boolean showPictures = false;
int imageSize = 0;
int textSize = 0;
int TopStringColor = Color. rgb (57, 66, 73);
boolean menuReady = false;
public MainMenu(FragmentActivity context, Draw d) {
super (context);
this. context = context;
setCanceledOnTouchOutside(true);
draw = d;
//GET DPI
DisplayMetrics dm = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(dm);
DPI = dm. densityDpi;
//SET PARAMETERS
showPictures =! (Boolean) Data. get (Data. interfaceSimplyBoolean ());
imageSize = DPI /5;
textSize = 17; //DPI / 20;
}
public void prepareMenu(){
boolean newShowPictures =!(Boolean)Data. get (Data. interfaceSimplyBoolean ());
if (menuView == null || showPictures!= newShowPictures) {
showPictures = newShowPictures;
MenuPreparer menuPreparer = new MenuPreparer();
menuPreparer.execute();
}
}
public void showMenu(){
if (! menuReady)
Logger. log ("showMenu Error: меню еще не готово.");
else {
show();
}
}
private void closeMenu(){
if (menuView == null)
Logger. log ("closeMenu Error: menuDialog = null.");
else
cancel();
//hide();
}
}
Лістинг 3.3.1 Клас MainMenu
Під час ініціалізації викликається функція prepareMenu, яка запускає фоновий потік, який займається ініціалізацією меню. Коли меню готове, прапорець menuReady приймає значення true. Коли користувач викликає меню, спрацьовує функція showMenu. Вона, використовуючи функцію системного класу, відображає меню на екрані. Якщо меню не готове, нічого не відбувається. Функція closeMenu закриває відкрите меню.
3.4 Інструмент «Пензель»
Пензель призначений для малювання на полотні, та представлений класом Brush, який, який наслідується від Instrument.
Під час вибору інструмента виконується ініціалізація внутрішніх змінних, що керують інструментом (лістинг 3.4.1).
@Override public void onSelect() {
branches = new HashMap<>();
pressureCoefficient = (Float)Data. get (Data. pressureCoefficientFloat ());
brushSize = (Integer)Data. get (Data. brushSizeInt ());
manageMethod = (Integer)Data. get (Data. manageMethodInt ());
smoothing = (Boolean)Data. get (Data. smoothingBoolean ());
smoothingHighQuality = (Boolean)Data. get (Data. smoothingHighQualityBoolean ());
antialiasing = (Boolean)Data. get (Data. antialiasingBoolean ());
brushColor = (Integer)Data. get (Data. brushColorInt ());
draw. undoAreaCalculator. reset();
}
Лістинг 3.4.1 Ініціалізація пензля
Під час дотику до екрану викликається обробник OnTouch() цього інструмента (лістинг 3.4.2)
event = draw. scale. scale_transformMotionEvent(event);
if (drawFirstHelp)
drawFirstHelp = false;
int action = event.getAction() & MotionEvent. ACTION_MASK;
invalidateAreaCalculator. reset();
if (action == MotionEvent. ACTION_DOWN || action == MotionEvent. ACTION_POINTER_DOWN){
int reasonIndex=(event.getAction() & MotionEvent. ACTION_POINTER_ID_MASK) >> MotionEvent. ACTION_POINTER_ID_SHIFT;
int reasonID = event.getPointerId(reasonIndex);
draw. undoAreaCalculator. add(event.getX(reasonIndex), event.getY(reasonIndex), brushSize);
Branch nextBranch;
branches. put(reasonID, nextBranch = new Branch());
nextBranch.down(event.getX(reasonIndex), event.getY(reasonIndex), event.getPressure(reasonIndex));
}
else if (action == MotionEvent. ACTION_MOVE) {
for (int i = 0; i < event.getPointerCount(); i++) {
int pointerID = event.getPointerId(i);
draw. undoAreaCalculator. add(event.getX(i), event.getY(i), brushSize);
if (branches. containsKey(pointerID))
branches. get(pointerID).move(event.getX(i), event.getY(i), event.getPressure(i));
}
}
if (action == MotionEvent. ACTION_UP || action == MotionEvent. ACTION_POINTER_UP){
int reasonIndex=(event.getAction() & MotionEvent. ACTION_POINTER_ID_MASK) >> MotionEvent. ACTION_POINTER_ID_SHIFT;
int reasonID = event.getPointerId(reasonIndex);
if (branches. containsKey(reasonID)) {
Branch branch = branches. get(reasonID);
branches. remove(reasonID);
branch.up(event.getX(reasonIndex), event.getY(reasonIndex), event.getPressure(reasonIndex));
}
draw. invalidate();
if (action == MotionEvent. ACTION_UP){
//сохранить шаг отмены, сбросить все что надо
draw. undoAreaCalculator. add((int)event.getX(), (int)event.getY(), (int) brushSize);
draw. undoAreaCalculator. check(draw. bitmap. getWidth(), draw. bitmap. getHeight());
draw. undoProvider. apply(draw. undoAreaCalculator. top, draw. undoAreaCalculator. bottom, draw. undoAreaCalculator. left, draw. undoAreaCalculator. right);
draw. undoProvider. prepare();
draw. undoAreaCalculator. reset();
}
}
if (draw. scale. scale_size == 1.0f &&! invalidateAreaCalculator. isEmpty())
draw. invalidate(invalidateAreaCalculator. left, invalidateAreaCalculator. top, invalidateAreaCalculator. right, invalidateAreaCalculator. bottom);
else
draw. invalidate();
Лістинг 3.4.2 Обробка дотику в інструменті «пензель»
Для реалізації цього інструмента були використані наступні джерела: [http://rusproject.narod.ru/android/simplepaint.htm], [http://korzh.net/2011-04-osnovy-raboty-s-2d-grafikoj-na-android.html], [http://developer.alexanderklimov.ru/android/], [http://geektimes.ru/post/107757/], [http://android-er.blogspot.com/2009/08/exercise-get-screen-resolution-using.html].
Для кожного окремого дотику на екрані створюється окремий екземпляр об’єкта Branch, який оброблює дії лише цього дотику (лістинг 3.4.3)
private class Branch{
private Paint paint;
private float lx = -1, ly = -1;
private float cx = -1, cy = -1; //для сглаживания
private float vx = 0, vy = 0;
private float m = 50;
private float rubbingCoefficient = 0.8f;
public Branch(){
paint = new Paint();
paint. setAntiAlias(antialiasing);
paint. setColor(brushColor);
paint. setStyle(Paint.Style. FILL);
paint. setStrokeWidth(brushSize);
}
public void down(float x, float y, float pressure){
setLast(x, y);
drawCircle(x, y, lastSize = brushSize(x, y, pressure));
}
public void move(float x, float y, float pressure){
if (Math. sqrt (x*x+y*y) < Data. store. DPI / 50)
return;
float size = smoothBrushSize
(brushSize(x, y, pressure));
if (smoothing)
drawSmoothedLine(lx, ly, x, y, size);
else {
drawCircle(x, y, size);
drawLine(lx, ly, x, y, size);
}
setLast(x, y);
}
public void up(float x, float y, float pressure){
float size = lastSize;
if (smoothing)
drawSmoothedLine(cx, cy, x, y, size);
else {
drawLine(lx, ly, x, y, size);
drawCircle(x, y, size);
}
}
private float lastSize = brushSize;
private void drawCircle(float x, float y, float size){
invalidateAreaCalculator. add(x, y, size);
draw. canvas. drawCircle(x, y, size/2f, paint);
}
private void drawLine(float x1, float y1, float x2, float y2, float size){
invalidateAreaCalculator. add(x1, y1, size);
invalidateAreaCalculator. add(x2, y2, size);
float totalParts = ((int)(Math. abs (size - lastSize) / 0.5f)) + 1;
for (float i = 0; i < totalParts; i++) {
float cx = x1 + (i * (x2-x1)/totalParts);
float ax = x1 + ((i+1) * (x2-x1)/totalParts);
if (i < totalParts -1)
ax += (ax-cx)/2;
float cy = y1 + (i * (y2-y1)/totalParts);
float ay = y1 + ((i+1) * (y2-y1)/totalParts);
if (i < totalParts -1)
ay += (ay-cy)/2;
float curSize = lastSize + (i * ((size - lastSize)/totalParts));
paint. setStrokeWidth(curSize);
draw. canvas. drawLine(cx, cy, ax, ay, paint);
}
lastSize = size;
}
private void drawSmoothedLine(float x1, float y1, float x2, float y2, float size){
float lcx = cx, lcy = cy;
float iterations = 5;
for (float i = 0; i < iterations; i++) {
float Fx = x2- cx;
float Fy = y2- cy;
float ax = Fx / m;
float ay = Fy / m;
vx *= rubbingCoefficient;
vy *= rubbingCoefficient;
vx += ax;
vy += ay;
cx += vx;
cy += vy;
if (lcx!= cx || lcy!= cy){
float curSize = lastSize + (size - lastSize)*(i / iterations);
drawCircle(cx, cy, curSize);
drawLine(lcx, lcy, cx, cy, curSize);
}
lcx = cx;
lcy = cy;
}
}
private void setLast(float x, float y){
if (cx == -1){
cx = x;
cy = y;
}
lx = x;
ly = y;
}
private float brushSize(float x, float y, float pressure){
if (manageMethod == Data. MANAGE_METHOD_PRESSURE){
return (brushSize * (pressure/ pressureCoefficient));
} else if (manageMethod == Data. MANAGE_METHOD_SPEED){
float dx = lx - x;
float dy = ly - y;
float d = (float)Math. sqrt (dx*dx + dy*dy);
return (brushSize / Math. max (1, (d/(float)Data. store. DPI * 50f)));
} else {
return brushSize;
}
}
private float smoothBrushSize(float in){
return lastSize + (in - lastSize) * 0.3f;
}
}
Лістинг 3.4.3 Клас Branch, що обробляє один дотик до екрану
Також всередині класу Branch реалізовано згладжування та динамічне керування товщиною пензля.
3.5 Інструмент «Гумка»
Інструмент призначений для стирання окремих фрагментів малюнка та реалізований у класі «Eraser». Під час вибору інстнумента виконується ініціалізація його основних змінних (лістинг 3.5.1).
public void onSelect() {
eraserSize = (Integer)Data. get (Data. eraserSizeInt ());
backgroundColor = (Integer)Data. get (Data. backgroundColorInt ());
historyProvider = new HistoryProvider(2, draw. MULTITOUCH_MAX);
//load
paint. setAntiAlias(false);
paint. setXfermode(new PorterDuffXfermode(PorterDuff.Mode. CLEAR));
paint. setStyle(Paint.Style. FILL);
paint. setStrokeWidth(eraserSize);
}
Лістинг 3.5.1 Ініціалізація гумки
Коли користувач веде по екрану, виконується малювання лінії заданої для пензля товщини, з урахуванням підтримки стирання кількома пальцями (лістинг 3.5.2)
int action = event.getAction() & MotionEvent. ACTION_MASK;
if (action == MotionEvent. ACTION_DOWN || action == MotionEvent. ACTION_POINTER_DOWN){
invalidateAreaCalculator. reset();
for (int pointer=0;pointer<event.getPointerCount();pointer++){
draw. canvas. drawCircle(event.getX(pointer), event.getY(pointer), (float) eraserSize / 2, paint);
invalidateAreaCalculator. add((int) event.getX(pointer), (int) event.getY(pointer), (int) eraserSize);
undoAreaCalculator. add((int)event.getX(pointer), (int)event.getY(pointer), (int) eraserSize);
}
historyProvider. write(event);
if (draw. scale. scale_size == 1.0f)
draw. invalidate(invalidateAreaCalculator. left, invalidateAreaCalculator. top, invalidateAreaCalculator. right, invalidateAreaCalculator. bottom);
else
draw. invalidate();
} else if (action == MotionEvent. ACTION_MOVE){
invalidateAreaCalculator. reset();
for (int pointer=0;pointer<event.getPointerCount();pointer++){
if (historyProvider. getX(event.getPointerId(pointer), 0) == -1) {
for (int i=0; i< historyProvider. historySize; i++)
historyProvider. write(event);
} draw. canvas. drawLine(historyProvider. getX(event.getPointerId(pointer),0), historyProvider. getY(event.getPointerId(pointer), 0), event.getX(pointer), event.getY(pointer), paint);
draw. canvas. drawCircle(event.getX(pointer), event.getY(pointer), (float) eraserSize / 2, paint);
invalidateAreaCalculator. add((int)event.getX(pointer), (int)event.getY(pointer), (int) eraserSize);
invalidateAreaCalculator. add((int) historyProvider. getX(event.getPointerId(pointer), 0), (int) historyProvider. getY(event.getPointerId(pointer), 0), (int) eraserSize);
undoAreaCalculator. add((int)event.getX(pointer), (int)event.getY(pointer), (int) eraserSize);
}
historyProvider. write(event);
if (draw. scale. scale_size == 1.0f)
draw. invalidate(invalidateAreaCalculator. left, invalidateAreaCalculator. top, invalidateAreaCalculator. right, invalidateAreaCalculator. bottom);
else
draw. invalidate();
} else if (event.getAction() == MotionEvent. ACTION_UP || action == MotionEvent. ACTION_POINTER_UP){
if (event.getAction() == MotionEvent. ACTION_UP) {
undoAreaCalculator. add((int) event.getX(), (int) event.getY(), (int) eraserSize);
undoAreaCalculator. check(bitmap. getWidth(), bitmap. getHeight());
draw. undoProvider. apply(undoAreaCalculator. top, undoAreaCalculator. bottom, undoAreaCalculator. left, undoAreaCalculator. right);
draw. undoProvider. prepare();
undoAreaCalculator. reset();
}
}
Лістинг 3.5.2 Обробка дотику інструментом «гумка»
Об’єкт undoAreaCalculator відповідає за збередення області в якій було виконано зміни, а invalidateAreaCalculator розраховує область екрана, яку треба оновити, щоб користувач побачив зміни на екрані.
3.6 Інструмент «Зафарбовування»
Інструмент призначений для зміни кольору однорідної області на малюнку та реалізований в класі Filler. Коли користувач натискає на екран, щей дотик обробляється функцією onTouch(), яка і запускає процес зафарбовування (лістинг 3.6.1).
if (! fillingInProgress && event.getX() < bitmap. getWidth() &&
event.getY() < bitmap. getHeight() && event.getX() > 0 && event.getY() > 0) {
int action = event.getAction() & MotionEvent. ACTION_MASK;
if (action == MotionEvent. ACTION_UP || action == MotionEvent. ACTION_POINTER_UP) {
int x=(int)event.getX(0);
int y=(int)event.getY(0);
if (color!= bitmap. getPixel(x, y)){
if (event.getPointerCount() == 1){
Filler.FillerRecursivePixels filler= new Filler.FillerRecursivePixels(bitmap, x, y,
color, context,
Data. tools. getResource(R.string. saveMenuCancel),
Data. tools. getResource(R.string. instrumentFilling));
filler.execute();
fillingInProgress = true;
}
}
}
}
return true;
Лістинг 3.6.1 Обробка дотику інструментом «зафарбовування»
Зафарбовування виконується в окремому потоці, в той час як на екрані відображається візуалізація процесу. Код, який виконується в потоці зафарбовування відображений в лістингу 2.8.2.
3.7 Інструмент «Масштабування»
Інструмент призначений для збільшення фрагменту малюнка та роботи з ним в збільшеному вигляді. Основна частина програми, що відповідає за масштабування, знаходиться в класі Scale.
Масштабуванням керують змінні, представлені в лістингу 3.7.1.
public float scale_size = 1.0f;
public float scale_offset_x = 0;
public float scale_offset_y = 0;
Лістинг 3.7.1 Головні змінні, які керують масштабуванням
Ці змінні складають інформаційну основу масштабування, адже в них міститься множник масштабу (де 1 = 100%), та координатні зміщення по кодній з осей.
Клас Scale містить набір функцій, передбачених для перетворення координат з екранної системи координат в систему координат малюнка за умови, що малюнок може бути збільшено (лістинг 3.7.2)
public float ScreenToImageX(float screenX) { /*IMAGE <- SCREEN*/
return ((screenX - scale_offset_x)/ scale_size);
}
public float ScreenToImageY(float screenY) { /*IMAGE <- SCREEN*/
return ((screenY - scale_offset_y)/ scale_size);
}
public float ImageToScreenX(float imageX){ /*IMAGE -> SCREEN*/
float out = imageX;
out *= scale_size;
out += scale_offset_x;
return out;
}
public float ImageToScreenY(float imageY){ /*IMAGE -> SCREEN*/
float out = imageY;
out *= scale_size;
out += scale_offset_y;
return out;
}
public MotionEvent scale_transformMotionEvent(MotionEvent in_event){
if (scale_size!= 1.0f) {
try {
int pointerCount = in_event.getPointerCount();
int [] pointerIDs = new int [pointerCount];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
for (int i=0; i < pointerCount; i++){
pointerCoords[i] = new MotionEvent.PointerCoords();
pointerIDs[i] = in_event.getPointerId(i);
in_event.getPointerCoords(i, pointerCoords[i]);
pointerCoords[i]. x = ScreenToImageX(pointerCoords[i]. x);
pointerCoords[i]. y = ScreenToImageY(pointerCoords[i]. y);
}
return MotionEvent. obtain (System. currentTimeMillis (), System. currentTimeMillis (), in_event.getAction(), in_event.getPointerCount(), pointerIDs, pointerCoords, in_event.getMetaState(), in_event.getXPrecision(), in_event.getYPrecision(), in_event.getDeviceId(), in_event.getEdgeFlags(), in_event.getSource(), in_event.getFlags());
} catch (Throwable throwable){
float x = ((in_event.getX() - scale_offset_x)/ scale_size);
float y = ((in_event.getY() - scale_offset_y)/ scale_size);
return MotionEvent. obtain (System. currentTimeMillis (), System. currentTimeMillis (), in_event.getAction(), x, y, 0);
}
}
else
return in_event;
}
Лістинг 3.7.2 Функції перетворення координат для масштабування
Ці функції є загальнодоступними для всіх компонентів програми та використовуються у всіх інструментах, які коректно працюють з масштабом. В коді інструмента вони зазвичай викликаються першими, для перетворення координат перед їх обробкою.
Обробка процесу масштабування зображена в лістингу 3.7.3.
int pointer_count = event.getPointerCount();
if (action == MotionEvent. ACTION_POINTER_DOWN && pointer_count == 2){
scale_touch_size = scale_size;
scale_touch_offset_x = scale_offset_x;
scale_touch_offset_y = scale_offset_y;
scale_touch_0_x = event.getX(scale_getPointerIndex(event, 0));
scale_touch_0_y = event.getY(scale_getPointerIndex(event, 0));
scale_touch_1_x = event.getX(scale_getPointerIndex(event, 1));
scale_touch_1_y = event.getY(scale_getPointerIndex(event, 1));
}
else if (action == MotionEvent. ACTION_MOVE && pointer_count == 2){
//scaling
float dx = Math. abs (event.getX(scale_getPointerIndex(event, 0)) - event.getX(scale_getPointerIndex(event, 1)));
float dy = Math. abs (event.getY(scale_getPointerIndex(event, 0)) - event.getY(scale_getPointerIndex(event, 1)));
if (dx == 0 || dy == 0)
return true;
float d_now = (float)Math. sqrt (dx*dx+dy*dy);
dx = Math. abs (scale_touch_0_x - scale_touch_1_x);
dy = Math. abs (scale_touch_0_y - scale_touch_1_y);
if (dx == 0 || dy == 0)
return true;
float d_old = (float)Math. sqrt (dx*dx+dy*dy);
scale_size = scale_touch_size * (d_now/d_old);
scale_checkSize();
//moving
//calculate direct moving
float cx_old = (scale_touch_0_x + scale_touch_1_x) / 2;
float cy_old = (scale_touch_0_y + scale_touch_1_y) / 2;
float cx_now = (event.getX(scale_getPointerIndex(event, 0)) + event.getX(scale_getPointerIndex(event, 1))) / 2;
float cy_now = (event.getY(scale_getPointerIndex(event, 0)) + event.getY(scale_getPointerIndex(event, 1))) / 2;
dx = cx_now - cx_old;
dy = cy_now - cy_old;
//calculate scaling offset
float now_onImageOffset_X = ((cx_old - scale_touch_offset_x)/ scale_touch_size); //КООРДИНАТЫ НА РИСУНКЕ, БЛЯТЬ!
float now_onImageOffset_Y = ((cy_old - scale_touch_offset_y)/ scale_touch_size);
float oldSizeX = now_onImageOffset_X * scale_touch_size;
float oldSizeY = now_onImageOffset_Y * scale_touch_size;
float newSizeX = now_onImageOffset_X * scale_size;
float newSizeY = now_onImageOffset_Y * scale_size;
dx -= (newSizeX - oldSizeX);
dy -= (newSizeY - oldSizeY);
//apply offset
scale_offset_x = scale_touch_offset_x + dx;
scale_offset_y = scale_touch_offset_y + dy;
scale_checkOffset();
draw. invalidate();
}
else if (action == MotionEvent. ACTION_POINTER_UP && pointer_count == 2){
scale_move_lock = true;
}
else if (action == MotionEvent. ACTION_DOWN && pointer_count == 1){
scale_touch_size = scale_size;
scale_touch_offset_x = scale_offset_x;
scale_touch_offset_y = scale_offset_y;
scale_touch_0_x = event.getX(scale_getPointerIndex(event, 0));
scale_touch_0_y = event.getY(scale_getPointerIndex(event, 0));
scale_move_lock = false;
}
else if (action == MotionEvent. ACTION_MOVE && pointer_count == 1 &&! scale_move_lock){
//moving
float cx_old = scale_touch_0_x;
float cy_old = scale_touch_0_y;
float cx_now = event.getX(scale_getPointerIndex(event, 0));
float cy_now = event.getY(scale_getPointerIndex(event, 0));
float dx = cx_now-cx_old;
float dy = cy_now-cy_old;
scale_offset_x = scale_touch_offset_x + dx;
scale_offset_y = scale_touch_offset_y + dy;
scale_checkOffset();
draw. invalidate();
}
Лістинг 3.7.3. Обробка дій користувача в процесі масштабування
Коли на екран поміщуються два пальці, їх координати записуються в пам’ять. При їх переміщенні виконується розрахунок нових даних для масштабування та для переміщення масштабованого малюнка.
При використанні одного пальця, виколується лише переміщення малюнка.
Дата добавления: 2015-08-09; просмотров: 88 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Меню жестів | | | Модуль відміни |