Hilfe: Auswahlrad - Auswahlkreis

  • Antworten:8
Sören Franke
  • Forum-Beiträge: 5

18.06.2016, 11:54:23 via Website

Hallo an alle erst mal,

ich glaube als Neuling in diesem Forum habe ich eine spezielle Frage. Das Internet konnte mir auf jeden Fall bisher nicht weiterhelfen. Also wende ich mich nun an euch :)

Ich bin gerade in meiner ersten App und jetzt mal unabhängig vom Kontext, möchte ich ein Auswahlrad erschaffen.
Das Ganze teste ich in einer eigenen Activity:

Ich habe einen Button in der Mitte, sobald dieser gedrückt und gehalten wird, erscheinen 8 ImageViews um diesen Button. Im einzelnen sehen diese Bilder(png) aus wie "Blütenblätter" (abstrakt) und zusammen Bilden sie einen Kreis.
Der Benutzer sollte sobald er den Button gedrückt hält sich für einen der Streifen entscheiden können, indem er mit dem Finger darüber streift und loslässt.

Den Button habe ich mit einem onTouchListener implementiert und darin die Anzeige und das Ausblenden der Bilder. Nur - habe ich festgestellt - bin ich ja während ich den Button noch gedrückt halte immer im Fokus des Buttons.

Ich habe bisher nicht herausgefunden wie ich in diesem onTouchListener des Buttons es hin bekomme, zu überprüfen über welchen Streifen/Bild sich der Finger gerade bewegt bzw. losgelassen wird.

Ich hoffe das war verständlich und das ihr mir weiterhelfen könnt.

Beste Grüße

Sören

— geändert am 18.06.2016, 11:58:15

Antworten
pepperonas
  • Forum-Beiträge: 434

18.06.2016, 14:32:52 via Website

Hmm, etwas vergleichbares hab ich selbst noch nicht gemacht, deswegen meine Antwort jetzt nur als Idee (und nicht als Lösung) verstehen..
Gibt definitiv mehrere Möglichkeiten das zu implementieren…
So auf die Schnelle fallen mir 2 Alternativen ein:

A. so wie du es aktuell machst und beim swipen nach außen (so verstehe ich deine Frage jedenfalls), beim Button clearFocus() aufrufen (bzw. wahlweise auf einer anderen View den Focus mit requestFocus() setzen), die Activity müsste also mit einem SwipeListener ausgestattet sein.
B. ein Abfang-Layout über die GUI legen und dort die Eingaben abfragen und ggf. die Events der darunterliegenden Views manuell triggern.

Eine CustomView hinsichtlich deines "Aktions-Layouts" zu erstellen ist bei deinem Vorhaben wahrscheinlich hilfreich.

Ansonsten möchte ich dir noch die Doku ans Herz legen: https://developer.android.com/training/gestures/viewgroup.html

PS: Ich würde aus dem reinen Bauchgefühl sagen, dass B) (also das Abfangen der Events auf einem unsichtbaren und den Eingabebereich überspannenden Frame) die bessere Möglichkeit ist, alles andere führt höchstwahrscheinlich zu x Sonderfällen, bei dem dann wieder irgendein anderer nicht getriggert wird.

— geändert am 18.06.2016, 14:41:49

Open Source

Sören Franke

Antworten
Sören Franke
  • Forum-Beiträge: 5

19.06.2016, 10:15:02 via Website

Erst mal vielen Dank für die guten Ansätze!! :)

Ich verstehe genau warum Idee A nicht unbedingt hilfreicher ist, als es die Situation noch komplizierter macht. Beim zweiten Ansatz verstehe ich auch auf was du hinaus willst. Ein paar Fragen stellen sich mir da vorerst trotzdem noch.

Soweit wie ich das verstanden habe ist der Fokus ja solange auf dem Button (der View) von welcher er getriggert wurde. Angenommen ich lege ein Abfang-Layout darüber, das sich nur aktiviert sobald der Button gedrückt wird. Wäre ich da aber nicht bei genau dem gleichen Problem wie ohne dem extra Layout? So oder so liegt ja noch der Fokus auf dem Button, oder täusche ich mich da?
Bzw. Wie könnte die abfrage IN DEM onTouchListener des Buttons grob lauten?
Pseudo:
Button onTouchListener{
[...]
case MotionEvent.Action_UP:
"hier noch ein switch case das auf irgendeine Art dann die Abfrage hat auf welchen der 8 Images losgelassen wird bzw. welcher er zuletzt berührt hat."

Also ich hab mich da mal etwas eingelesen und gelernt das der Fokus nur das eine Element sieht das in Aktion ist und alle anderen Views etc dabei komplett ausgeblendet sind. Ich finde auch den Link klasse den du mir noch geschickt hast. Ich hab die Vermutung das es gut klappen könnte wenn ich selbst eine Art "Button" schreibe, der eben nicht nur aus dem einen Feld besteht, also dem Button selbst, sondern aus dem Button + 8 Auswahlfelder. Nur wie definiere ich so etwas - einen eigenen Button?

Ich höre mich noch etwas um, und vorerst noch einmal vielen Dank!

Beste Grüße

Sören

Antworten
pepperonas
  • Forum-Beiträge: 434

19.06.2016, 17:10:21 via Website

Hallo,
dein Pseudocode sieht soweit schon gut aus. Je nachdem wie umfangreich deine Logik dahinter sein soll, musst du anhand von verschachtelten Verzweigungen (switch, if, if-else) prüfen ob der Nutzer das gewüschte Verhalten ausführt. (bspw: "der Nutzer muss ein Rechteckt zeichnen" sähe dann so aus ("alt" bedeutet immer die vorhergehende Position): X darf nicht kleiner als X alt sein, sobald Y größer wird, muss Y größer sein als Y alt <- dann hätte man eine "Ecke" geprüft, ggf. solltest du noch etwas Toleranzen einbauen und das möglichst auch auf die Displayskalierung runterrechnen, damit sich die App auf jedem Bildschirm ähnlich verhält).
Hier findest du auch Hilfreiches https://developer.android.com/training/gestures/detector.html <- das Beispiel solltest du als grobe Vorlage schon nutzen können. Anhand der ActionEvents die dir die IDE vorschlägt und ein paar Log Ausgaben kommst du dahinter.

Einen CustomButton legst du so an:

public class CustomButton extends Button{

 // Kontruktor generieren lassen

// Shortcut für Generate "(Create constructors, Generate getter, setter / Override methods)" drücken
// die Methoden überschreiben, die du modifizieren willst
}

in xml.

<LinearLayout
android:layout_width=...
>

// ... 

<com.dein.packagename.CustomButton
android:layout_width=...
>

//...
</LinearLayout>

Ob ein Button wirklich das Richtige ist, möchte ich aus der Ferne fast bezweifeln. Da er mir von seiner "Beschaffenheit" wenig passend für dein Vorhaben vorkommt. Nur weil er er bei ActionUP triggert, ist er nicht die beste Wahl <- besagtes Triggern lässt sich auf jede View ebenso anwenden, daher würde hier ein LayoutContainer (GridLayout?) meiner Meinung nach mehr Flexibilität für dich bieten - kann aber auch falsch liegen, da musst du entweder mehr Infos rausgeben, oder dir selbst nochmal einen Plan machen (das musst du wahrscheinlich sowieso, da du das Konzept verstanden haben musst um es anzuwenden. Lohnt sich aber und ist auch nicht sooo schwierig) :)

— geändert am 19.06.2016, 17:12:36

Open Source

Sören Franke

Antworten
Sören Franke
  • Forum-Beiträge: 5

20.06.2016, 15:16:42 via Website

Also die Artikel passen sehr gut! Hab sie mir mal aufmerksam durchgelesen und komm zu dem Ergebnis, dass das soweit ich das sehe nicht an diesen Wegen vorbei führt.

So wie ich das verstehe komme ich aber anscheinend nicht an dem - nennen wir es mal Hardcodieren - vorbei oder? Ich hätte ja alles eigentlich schon hinbekommen können, würde ich mit selbst definierten Bereichen (Winkel des Pointers + Abstand zum Zentrum) arbeiten. Nur dachte ich eben an die Displaykompatibilität der untersch. Geräte.
Daher der Gedanke mit dem Abfragen auf welchem Objekt losgelassen wird.
Mit dem GestureDetector bekomme ich auf jeden Fall die Gestiken hin usw. was aber IMHO mit Koordinaten und selbst definierten Grenzen verbunden ist.

Ich geb gerne dazu etwas mehr Info raus :)
Hier einmal meine Testactivity (ich kopiere es einfach rein, wenn das korrekt ist?):

P.S. Ich hab auch einmal was von setTag getTag gehört, hab damit noch nichts weiter gemacht und die Infos aus dem Internet geben mir auch nicht wirklich Aufschluss auf welche Art und Weise mir das hilfreich sein kann. Habt ihr/du dazu eine Meinung?

    package fraso.projectsoula;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

//Imports erst einmal vernachlässigen, das kommt vom Probieren

public class test_stripe extends AppCompatActivity {

    float btOX;
    float btOY;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_stripes);

        final ImageView stripe1 = (ImageView) findViewById(R.id.stripe1);
        final ImageView stripe2 = (ImageView) findViewById(R.id.stripe2);
        final ImageView stripe3 = (ImageView) findViewById(R.id.stripe3);
        final ImageView stripe4 = (ImageView) findViewById(R.id.stripe4);
        final ImageView stripe5 = (ImageView) findViewById(R.id.stripe5);
        final ImageView stripe6 = (ImageView) findViewById(R.id.stripe6);
        final ImageView stripe7 = (ImageView) findViewById(R.id.stripe7);
        final ImageView stripe8 = (ImageView) findViewById(R.id.stripe8);
        final Button btO = (Button) findViewById(R.id.bt_stripes);

        // Rotation der einzelen Bilder (Photoshopbedingt)
        stripe3.setRotation(90);
        stripe5.setRotation(180);
        stripe7.setRotation(270);
        stripe2.setRotation(45);
        stripe4.setRotation(135);
        stripe6.setRotation(225);
        stripe8.setRotation(315);

       //Der folgende Part wird benötigt damit der Button vorgerendert wird und ich die Koordinaten abrufen kann.
      //Die Koordinaten werden dann dazu verwendet um von dort aus die Stripes in einem Kreis anzuordnen. Sonst immer x: 0 y: 0

        ViewTreeObserver vto = btO.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {

                int[] loc = new int[2];
                btO.getLocationOnScreen(loc);
                btOX = loc[0]+btO.getWidth()/2;
                btOY = loc[1]+btO.getHeight()/2;

                //Das mag etwas verwirrend aussehen, ist aber nur die Platzierung der einzelnen, bereits gedrehten Stripes in einem Kreis um das
                //Objekt/ dem Button

                stripe1.setX(loc[0] + ( btO.getWidth() /2 ) - stripe1.getWidth() /2 );
                stripe1.setY(loc[1] - stripe1.getHeight());

                stripe2.setX(loc[0] + (btO.getWidth() /2 ) - (stripe1.getWidth() /2 ) + (stripe1.getWidth() /2 ) + (stripe1.getWidth() /3 ));
                stripe2.setY(loc[1] - stripe1.getHeight() + (stripe1.getWidth() /3 ));

                stripe3.setX(loc[0] + (btO.getWidth() /2 ) - (stripe3.getWidth() /2 ) + stripe3.getHeight() /2 );
                stripe3.setY(loc[1] - (stripe3.getHeight() /2 ));

                stripe4.setX(loc[0] + (btO.getWidth() /2 ) - (stripe1.getWidth() /2 ) + (stripe1.getWidth() /2 ) + (stripe1.getWidth() /3 ));
                stripe4.setY(loc[1] - (stripe1.getWidth() /3 ));

                stripe5.setX(loc[0] + (btO.getWidth() /2 ) - stripe1.getWidth() /2 );
                stripe5.setY(loc[1]);

                stripe6.setX(loc[0] + (btO.getWidth() /2 ) - (stripe1.getWidth() /2 ) - (stripe1.getWidth() /2 ) - (stripe1.getWidth() /3 ));
                stripe6.setY(loc[1] - (stripe1.getWidth() /3 ));

                stripe7.setX(loc[0] + (btO.getWidth() /2 ) - (stripe1.getWidth() /2 ) - stripe1.getHeight() /2 );
                stripe7.setY(loc[1] - stripe1.getHeight() /2 );

                stripe8.setX(loc[0] + (btO.getWidth() /2 ) - (stripe1.getWidth() /2 ) - (stripe1.getWidth() /2 ) - (stripe1.getWidth() /3 ));
                stripe8.setY(loc[1] - stripe1.getHeight() + (stripe1.getWidth() /3 ));

            }
        });

       //Hier kommt jetzt der OnTouchListener

        btO.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                final float dx;
                int i=0;
                final float dy;
                float distance = 0;
                stripe1.setClickable(true);

                switch(event.getAction()) {
                    case MotionEvent.ACTION_DOWN:

                           //Stripes werden angezeigt

                            stripe1.animate().alpha(1).setDuration(200);
                            stripe2.animate().alpha(1).setDuration(200);
                            stripe3.animate().alpha(1).setDuration(200);
                            stripe4.animate().alpha(1).setDuration(200);
                            stripe5.animate().alpha(1).setDuration(200);
                            stripe6.animate().alpha(1).setDuration(200);
                            stripe7.animate().alpha(1).setDuration(200);
                            stripe8.animate().alpha(1).setDuration(200);

                        break;

                    case MotionEvent.ACTION_UP:

                            //Das hier ist sehr wichtig für mich gewesen, hab es aber schon hinbekommen,
                           //letztendlich wird hier die Entfernung bemessen beim Action_UP zum Zentrum.
                           //Ein Stripe ist bei mir in drei Segmente unterteilt. Ich musste bestimmen auf welchem Segment losgelassen wurde.
                          //Das hab ich "hardcodiert" mit der Entfernung zum Zentrum des Buttons bewerkstelligt. Aber erst mal nicht relevant

                            dx = event.getRawX();
                            dy = event.getRawY();

                            distance = (float) Math.sqrt((Math.pow(Math.abs(dx-btOX),2))+(Math.pow(Math.abs(dy-btOY),2)));

                            if(distance<=50) {
                                i = 0;
                }
                            else if(distance>50 && distance<=195) {
                                i = 1;
                            }
                            else if(distance>195 && distance<=360) {
                                i = 2;
                            }
                            else if(distance>360 && distance<535){
                                i = 3;
                            }
                            else if(distance>535) {
                                i = 4;}

                            switch (i){
                                case 0:
                                    Toast.makeText(test_stripe.this, "Mitte", Toast.LENGTH_SHORT).show();
                                    break;
                                case 1:
                                    Toast.makeText(test_stripe.this, "1. Quadrant", Toast.LENGTH_SHORT).show();
                                    break;
                                case 2:
                                    Toast.makeText(test_stripe.this, "2. Quadrant", Toast.LENGTH_SHORT).show();
                                    break;
                                case 3:
                                    Toast.makeText(test_stripe.this, "3. Quadrant", Toast.LENGTH_SHORT).show();
                                    break;
                                case 4:
                                    Toast.makeText(test_stripe.this, "Außerhalb", Toast.LENGTH_SHORT).show();
                                    break;
                            }

                            stripe1.animate().alpha(0).setDuration(200);
                            stripe2.animate().alpha(0).setDuration(200);
                            stripe3.animate().alpha(0).setDuration(200);
                            stripe4.animate().alpha(0).setDuration(200);
                            stripe5.animate().alpha(0).setDuration(200);
                            stripe6.animate().alpha(0).setDuration(200);
                            stripe7.animate().alpha(0).setDuration(200);
                            stripe8.animate().alpha(0).setDuration(200);

                        break;


                }
                return false;

            }
        });

    }

}

— geändert am 20.06.2016, 15:18:15

Antworten
pepperonas
  • Forum-Beiträge: 434

20.06.2016, 17:20:26 via Website

Das sieht auch gut aus. Was funktioniert aktuell noch nicht, bzw. macht Probleme?

Anpassen auf die DPI-Werte kannst du deinen Code in dem du mit der Pixeldichte multiplizierst.

 getResources().getDisplayMetrics().density;

(sollte so stimmen, würde aber trotzdem vorher nochmal eine Plausibilitätsprüfung machen, also zB irgendwas mit dem Faktor vergrößern und dann auf zwei unterschiedlichen Emulatoren /Geräten schauen ob das Objekt in Relation gesehen auf beiden Geräten gleich groß ist. Oder nimmst einfach einen Zahlenwert und rechnest "selbst" nach)..

Open Source

Antworten
Sören Franke
  • Forum-Beiträge: 5

21.06.2016, 21:37:53 via Website

Wie schon gesagt, ich benötige neben der Anzeige während ich den Button gedrückt halte noch die Abfrage auf welcher View/ welchem Image ich dann loslasse.

Es sollte funktionieren wie ein gekapselter Button.

Sozusagen 1 Button der 8 weitere aktiviert und auswählbar macht indem ich nur mit dem Finger darüber wische und loslasse.

Antworten
Sören Franke
  • Forum-Beiträge: 5

23.06.2016, 14:20:07 via Website

Ich denke so kann man es auch nennen. Auswahlrad, Joystick ich denke das Beschreibt es ganz gut.

Als ähnliches Beispiel, was jedem bekannt sein sollte, wären die neuen "Likebutton" von Facebook. Sie sind zwar nicht in einem Kreis angeordnet, man muss aber auch gedrückt halten, dann öffnen sich die neuen Button, man fährt über einen mit dem Finger und lässt los.

Antworten