Hi Leute,
hab ein kleines Problem mit meinem Abschlussprojekt, eine kleine Malapp mit der man auf einem Canvas zeichnen kann. Dafür gibts verschiede Funktionen, wie Colorpicker, ne Seekbar für die Pinselgröße, speichern etc... Die App läuft soweit, vorallem mit guter Performance. Jetzt hätt ich allerdings noch gerne eine Funktion drin, die ich einfach nicht hinbekomme. Ich würde gerne ein Bild aus der Gallery laden und irgendwie als Hintergrund in den Canvas reinsetzen, sodass man quasi auf dem Bild malt. Beim speichern soll das Bild natürlich im Bitmap das erstellt wird enthalten sein. Wie krieg ich das am besten gelöst? Hier mal bissel Code (Die teilweise geilen Kommentar bitte ignorieren, sind nur zum Nachvollziehen des Codes für meine beiden Studienkollegen)
Die Klasse DrawView:
public class DrawView extends ImageView implements OnTouchListener
{
boolean drawMode = true; //Es wird im Vordergrund gezeichnet als Standard - für Background als Standard false setzen
boolean brushForm = true; //Es wird Standardmäßig der Runde Brush gewählt - für echigen als Standard false setzen
boolean drawCircle = false; //Prüft ob Kreis gemalt werden soll
boolean drawSquare = false; //Prüft ob Rechteck gemalt werden soll
//Anzahl der Finger die maximal verwendet werden fürs Multitouch
final int MAX_POINT_CNT = 2;
float[] x = new float[MAX_POINT_CNT]; //Array mit x-Werten der jeweiligen Fingerpositionen
float[] y = new float[MAX_POINT_CNT]; //Array mit y-Werten der jeweiligen Fingerpositionen
boolean[] isTouch = new boolean[MAX_POINT_CNT]; //Speichert welche Finger gerade aktiv sind
Path path = new Path();
Paint paint = new Paint();
Paint cursorPaint = new Paint();
Path cursorPath = new Path();
LinkedList<Path> pathList = new LinkedList<Path>(); //Liste der Paintobjekte, speichert Informationen zu den jeweiligen Brushes
LinkedList<Paint> paintList = new LinkedList<Paint>(); //Liste der Pfade die gezeichnet wurden. (jeweiliger Index von Pfad/Paintobjekt ist gleich)
LinkedList<Circle> circleList = new LinkedList<Circle>(); //Liste der Kreisobjekte die gezeichnet wurden
LinkedList<Square> squareList = new LinkedList<Square>(); //Liste der Rechteckobjekte die gezeichnet wurden
public DrawView(Context context)
{
super(context);
setDrawingCacheEnabled(true); //Aktiviert das Caching, wird zum Speichern benötigt
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
this.setBackgroundColor(0xffffffff); //Wird der BG nicht geändert, würde er beim Speichern schwarz sein, so isser Standardmäßig weiss
paint.setAntiAlias(true); //Macht schönere Ränder beim Brush, machts allerdings etwas langsamer
cursorPaint.setAntiAlias(true);
paint.setDither(true); //Höhere Farbechtheit, machts allerdings auch bissel langsamer
//Lade Standardbrush
setBrushStyle(Paint.Join.ROUND, Paint.Cap.ROUND, Paint.Style.STROKE);
setBrushSize(2);
setBrushColor(0xff000000);
}
//Zeichnet alles auf den Canvas
public void onDraw(Canvas canvas)
{
if(brushForm)
setBrushStyle(Paint.Join.ROUND, Paint.Cap.ROUND, Paint.Style.STROKE);
else
setBrushStyle(Paint.Join.BEVEL, Paint.Cap.SQUARE, Paint.Style.STROKE);
canvas.drawPath(cursorPath, cursorPaint);
if(drawMode)
{
for (int i=0; i < pathList.size(); i++) {
canvas.drawPath(pathList.get(i), paintList.get(i));
}
canvas.drawPath(path, paint);
}
else
{
canvas.drawPath(path, paint);
for (int i=0; i < pathList.size(); i++) {
canvas.drawPath(pathList.get(i), paintList.get(i));
}
}
}
//Down-Move-Up Touchevent-Handler
public boolean onTouch(View view, MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
int pointerIndex = ((event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT);
int pointerId = event.getPointerId(pointerIndex);
int action = (event.getAction() & MotionEvent.ACTION_MASK);
int pointCnt = event.getPointerCount();
if (pointCnt <= MAX_POINT_CNT) {
if (pointerIndex <= MAX_POINT_CNT - 1) {
for (int i = 0; i < pointCnt; i++) {
int id = event.getPointerId(i);
x[id] = (int) event.getX(i);
y[id] = (int) event.getY(i);
}
switch (action) {
case MotionEvent.ACTION_DOWN:
if(drawCircle || drawSquare)
isTouch[pointerId] = true;
else {
path = new Path();
path.reset();
path.moveTo(eventX, eventY);
}
return true;
case MotionEvent.ACTION_MOVE:
if(drawCircle || drawSquare)
isTouch[pointerId] = true;
else {
path.lineTo(eventX, eventY);
cursorPath.reset();
cursorPath.addCircle(eventX, eventY, 30, Path.Direction.CW);
}
break;
case MotionEvent.ACTION_UP:
if(drawCircle || drawSquare)
isTouch[pointerId] = false;
else {
cursorPath.reset();
Paint newPaint = new Paint();
newPaint.set(paint);
if(drawMode) {
pathList.addLast(path);
paintList.addLast(newPaint);
}
else {
pathList.addFirst(path);
paintList.addFirst(newPaint);
}
}
break;
default:
if(drawCircle || drawSquare)
isTouch[pointerId] = false;
return false;
}
}
}
invalidate(); //Aktualisiert die View
return true;
}
}
Alles anzeigen
Und noch die Activity:
public class MainActivity extends Activity {
private static int RESULT_LOAD_IMAGE = 1; //Wird vom Galleryintent verwendet
boolean isFileToShare = false; //Prüft ob die Datei im Share Ordner oder im normalen Ordner abgelegt werden soll
boolean isFileAlreadySaved = false; //Wurde das Bild schon gespeichert? Wird abgefragt wenn man die App schließt und evtl. einen Dialog zu öffnen
int actualColor = 0xffff0000; //Selbe Farbe wie in DrawView beim Laden des Standardbrushes, wird überschrieben bei Änderung durch colorPicker
String actualFileName = ""; //Wird benötigt um beim Aufruf von Share zu wissen welches Bild geteilt werden soll
Bitmap bmp = null; //Bild aus Galerie
DrawView drawView = null;
//Wird beim Starten ausgeführt
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Setzt App nach Start auf Fullscreen - Kann man überlegen das nochmal mit reinzunehmen, daher nur auskommentiert
//getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
this.setRequestedOrientation(1); //Verhindert das drehen der App, ColorPicker funzt nicht richtig in Landscapemode (0)
drawView = new DrawView(this);
setContentView(drawView);
}
//Generiert das Optionsmenu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.main_optionsmenu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//BEI ÄNDERUNGEN DIE REIHENFOLGE BITTE IN strings.xml und main_optionsmenu.xml der Übersicht wegen abgleichen
//HIER WERDEN AUCH NUR METHODEN AUFGERUFEN, ALSO KEINEN CODE IN DIE CASES SCHREIBEN -> METHODEN DEFINIEREN!!!
switch (item.getItemId()) {
case R.id.id_optionsmenue_save:
saveAsBitmap(); //Speichert gemaltes Bild im photoshapp Order der sich im root des Speichers befindet
break;
case R.id.id_optionsmenue_load:
loadAsBitmap(); //!!!!!! FUNZT NOCH NICHT RICHTIG !!!!!!!
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
//Nur relevant für das Laden eines Bildes über den Galleryintent
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) {
Uri selectedImage = data.getData();
String[] filePathColumn = { MediaStore.Images.Media.DATA };
Cursor cursor = getContentResolver().query(selectedImage,
filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String picturePath = cursor.getString(columnIndex);
cursor.close();
//Soll eigentlich das gewählte Bild aus dem Intent in die View laden... man sieht aber nix :D Keine Ahnung worans liegt
//Denke das Bild wird irgendwo hintendran geladen, oder garnicht, oder keine Ahnung was!
drawView.setImageBitmap(BitmapFactory.decodeFile(picturePath));
//Feedback um User zu informieren was passiert
Toast.makeText(getApplicationContext(), "Image loaded!",Toast.LENGTH_SHORT).show();
}
}
//Speichert den aktuellen Content im externen Speicher
public void saveAsBitmap() {
isFileAlreadySaved = true;
//Holt aktuelles Datum und schreibt es in einen String der weiter unten an Dateinamen drankommt
Calendar currentDate = Calendar.getInstance();
SimpleDateFormat formatter= new SimpleDateFormat("yyyyMMMddHmmss");
String dateNow = formatter.format(currentDate.getTime());
//Holt aktuellen DrawingCache (alles was bis jetzt gemalt wurde) und speichert ihn ins Bitmap
Bitmap bitmap = drawView.getDrawingCache();
//Fragt den Systempfad ab und erstellt den Appordner
String root = Environment.getExternalStorageDirectory().toString();
File myDir = null;
//Prüft ob das Bild normal gespeichert wurde oder ob es geteilt werden soll
if(isFileToShare) {
myDir = new File(root + "/PhotoshApp/Share");
}
else {
myDir = new File(root + "/PhotoshApp");
}
myDir.mkdirs();
String fname = "photoshapp_"+ dateNow + ".jpg";
actualFileName = root + "/PhotoshApp/Share/" + fname; //Speichert aktuellen Pfad inkl. Dateinamen als String (fürs Teilen benötigt)
File file = new File (myDir, fname);
if (file.exists ()) file.delete ();
try {
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out); //Bild wird als JPEG mit Qualität 90 gespeichert
//Feedback um User zu informieren was passiert
Toast.makeText(getApplicationContext(), "Image saved!",Toast.LENGTH_SHORT).show();
out.flush();
out.close();
} catch (Exception e) {
//Feedback um User zu informieren was passiert - Gibt Exception im Toast aus
Toast.makeText(getApplicationContext(), e.toString(),Toast.LENGTH_SHORT).show();
}
}
//Öffnet den Galleryintent um ein Bild aus der Gallery zu wählen
public void loadAsBitmap() {
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, RESULT_LOAD_IMAGE);
}
}
Alles anzeigen
Hat jemand Rat für mich? Momentan passiert jedenfalls nichts. Wenn die App abstürzen würde, wüsst ich wenigstens dass was falsch ist Aber so passiert einfach garnichts