Comment puis-je mettre en œuvre un contrôle circulaire par balayage comme celui-ci?

je travaille sur une application Android, et j'ai un TextView où j'affiche un prix (par exemple 50$).

je voudrais avoir un contrôle circulaire semblable à cette image:

enter image description here

  • glisser un doigt dans le sens des aiguilles d'une montre sur le cadran augmente le montant de $1 pas
  • Glisser un doigt dans le sens antihoraire sur le cadran diminue la quantité de 1 $par étapes

j'ai fait quelques recherches mais impossible de trouver un travail mise en œuvre de quelque chose pour faire cela.

Comment Pouvez-vous créer un tel contrôle circulaire piloté par des glissières?

18
demandé sur Brad Larson 2014-03-10 15:51:09

10 réponses

j'ai modifié la source de circularseekbar pour fonctionner comme vous le souhaitez.

vous pouvez obtenir la classe mofidied de modifié cirucularseekbar

tout d'abord inclure le contrôle dans votre mise en page et définir votre cadran comme un arrière-plan

            <com.yourapp.CircularSeekBar
                android:id="@+id/circularSeekBar"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/amount_wheel_bg" />

puis, dans votre activité (il devrait implémenter OnCircularSeekBarChangeListener) ajouter ce qui suit:

//This is a reference to the layout control
private CircularSeekBar circularSeekBar;
//This is a reference to the textbox where you want to display the amount
private EditText amountEditText;
private int previousProgress = -1;

Et ajouter le rappel suivante méthodes:

@Override
   public void onProgressChanged(CircularSeekBar circularSeekBar,
                 int progress, boolean fromUser) {
          if(previousProgress == -1)
          {
                 //This is the first user touch we take it as a reference
                 previousProgress = progress;
          }
          else
          {
                 //The user is holding his finger down
                 if(progress == previousProgress)
                 {
                       //he is still in the same position, we don't do anything
                 }
                 else
                 {
                       //The user is moving his finger we need to get the differences

                       int difference = progress - previousProgress;                        

                       if(Math.abs(difference) > CircularSeekBar.DEFAULT_MAX/2)
                       {
                              //The user is at the top of the wheel he is either moving from the 0 -> MAx or Max -> 0
                              //We have to consider this as 1 step 

                              //to force it to be 1 unit and reverse sign;
                              difference /= Math.abs(difference); 
                              difference -= difference;

                       }                          
                       //update the amount
                       selectedAmount += difference;
                        previousProgress= progress;
                       updateAmountText();
                 }
          }

   }

   @Override
   public void onStopTrackingTouch(CircularSeekBar seekBar) {

          //reset the tracking progress
          previousProgress = -1;

   }

   @Override
   public void onStartTrackingTouch(CircularSeekBar seekBar) {

   }

   private void updateAmountText()
   {
          amountEditText.setText(String.format("%.2f", selectedAmount));
   }

selectedAmount est une double propriété pour stocker le montant sélectionné.

j'espère que cela peut vous aider.

9
répondu Maystro 2014-03-19 13:25:42

Classe DialView:

public abstract class DialView extends View {

    private float centerX;
    private float centerY;
    private float minCircle;
    private float maxCircle;
    private float stepAngle;

    public DialView(Context context) {
        super(context);
        stepAngle = 1;
        setOnTouchListener(new OnTouchListener() {
            private float startAngle;
            private boolean isDragging;
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                float touchX = event.getX();
                float touchY = event.getY();
                switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    startAngle = touchAngle(touchX, touchY);
                    isDragging = isInDiscArea(touchX, touchY);
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (isDragging) {
                        float touchAngle = touchAngle(touchX, touchY);
                        float deltaAngle = (360 + touchAngle - startAngle + 180) % 360 - 180;
                        if (Math.abs(deltaAngle) > stepAngle) {
                            int offset = (int) deltaAngle / (int) stepAngle;
                            startAngle = touchAngle;
                            onRotate(offset);
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    isDragging = false;
                    break;
                }
                return true;
            }
        });
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        centerX = getMeasuredWidth() / 2f;
        centerY = getMeasuredHeight() / 2f;
        super.onLayout(changed, l, t, r, b);
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        float radius = Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2f;
        Paint paint = new Paint();
        paint.setDither(true);
        paint.setAntiAlias(true);
        paint.setStyle(Style.FILL);
        paint.setColor(0xFFFFFFFF);
        paint.setXfermode(null);
        LinearGradient linearGradient = new LinearGradient(
            radius, 0, radius, radius, 0xFFFFFFFF, 0xFFEAEAEA, Shader.TileMode.CLAMP);
        paint.setShader(linearGradient);
        canvas.drawCircle(centerX, centerY, maxCircle * radius, paint);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        canvas.drawCircle(centerX, centerY, minCircle * radius, paint);
        paint.setXfermode(null);
        paint.setShader(null);
        paint.setColor(0x15000000);
        for (int i = 0, n =  360 / (int) stepAngle; i < n; i++) {
            double rad = Math.toRadians((int) stepAngle * i);
            int startX = (int) (centerX + minCircle * radius * Math.cos(rad));
            int startY = (int) (centerY + minCircle * radius * Math.sin(rad));
            int stopX = (int) (centerX + maxCircle * radius * Math.cos(rad));
            int stopY = (int) (centerY + maxCircle * radius * Math.sin(rad));
            canvas.drawLine(startX, startY, stopX, stopY, paint);
        }
        super.onDraw(canvas);
    }

    /**
     * Define the step angle in degrees for which the
     * dial will call {@link #onRotate(int)} event
     * @param angle : angle between each position
     */
    public void setStepAngle(float angle) {
        stepAngle = Math.abs(angle % 360);
    }

    /**
     * Define the draggable disc area with relative circle radius
     * based on min(width, height) dimension (0 = center, 1 = border)
     * @param radius1 : internal or external circle radius
     * @param radius2 : internal or external circle radius
     */
    public void setDiscArea(float radius1, float radius2) {
        radius1 = Math.max(0, Math.min(1, radius1));
        radius2 = Math.max(0, Math.min(1, radius2));
        minCircle = Math.min(radius1, radius2);
        maxCircle = Math.max(radius1, radius2);
    }

    /**
     * Check if touch event is located in disc area
     * @param touchX : X position of the finger in this view
     * @param touchY : Y position of the finger in this view
     */
    private boolean isInDiscArea(float touchX, float touchY) {
        float dX2 = (float) Math.pow(centerX - touchX, 2);
        float dY2 = (float) Math.pow(centerY - touchY, 2);
        float distToCenter = (float) Math.sqrt(dX2 + dY2);
        float baseDist = Math.min(centerX, centerY);
        float minDistToCenter = minCircle * baseDist;
        float maxDistToCenter = maxCircle * baseDist;
        return distToCenter >= minDistToCenter && distToCenter <= maxDistToCenter;
    }

    /**
     * Compute a touch angle in degrees from center
     * North = 0, East = 90, West = -90, South = +/-180
     * @param touchX : X position of the finger in this view
     * @param touchY : Y position of the finger in this view
     * @return angle
     */
    private float touchAngle(float touchX, float touchY) {
        float dX = touchX - centerX;
        float dY = centerY - touchY;
        return (float) (270 - Math.toDegrees(Math.atan2(dY, dX))) % 360 - 180;
    }

    protected abstract void onRotate(int offset);

}

Utilisation :

public class DialActivity extends Activity {

    @Override
    protected void onCreate(Bundle state) {
        setContentView(new RelativeLayout(this) {
            private int value = 0;
            private TextView textView;
            {
                addView(new DialView(getContext()) {
                    {
                        // a step every 20°
                        setStepAngle(20f);
                        // area from 30% to 90%
                        setDiscArea(.30f, .90f);
                    }
                    @Override
                    protected void onRotate(int offset) {
                        textView.setText(String.valueOf(value += offset));
                    }
                }, new RelativeLayout.LayoutParams(0, 0) {
                    {
                        width = MATCH_PARENT;
                        height = MATCH_PARENT;
                        addRule(RelativeLayout.CENTER_IN_PARENT);
                    }
                });
                addView(textView = new TextView(getContext()) {
                    {
                        setText(Integer.toString(value));
                        setTextColor(Color.WHITE);
                        setTextSize(30);
                    }
                }, new RelativeLayout.LayoutParams(0, 0) {
                    {
                        width = WRAP_CONTENT;
                        height = WRAP_CONTENT;
                        addRule(RelativeLayout.CENTER_IN_PARENT);
                    }
                });
            }
        });
        super.onCreate(state);
    }

}

Résultat :

result

12
répondu alex 2014-03-19 23:04:35

je viens d'écrire le code suivant et testé uniquement théoriquement.

private final double stepSizeAngle = Math.PI / 10f; //Angle diff to increase/decrease dial by 1$
private final double dialStartValue = 50.0;

//Center of your dial
private float dialCenterX = 500;
private float dialCenterY = 500;

private float fingerStartDiffX;
private float fingerStartDiffY;

private double currentDialValueExact = dialStartValue;


public boolean onTouchEvent(MotionEvent event) {
    int eventaction = event.getAction();

    switch (eventaction) {
        case MotionEvent.ACTION_DOWN: 
            //Vector between startpoint and center
            fingerStartDiffX = event.getX() - dialCenterX;
            fingerStartDiffY = event.getY() - dialCenterY;
            break;

        case MotionEvent.ACTION_MOVE:
            //Vector between current point and center
            float xDiff = event.getX() - dialCenterX;
            float yDiff = event.getY() - dialCenterY;

            //Range from -PI to +PI
            double alpha = Math.atan2(fingerStartDiffY, yDiff) - Math.atan2(fingerStartDiffX, xDiff);

            //calculate exact difference between last move and current move.
            //This will take positive and negative direction into account.
            double dialIncrease = alpha / stepSizeAngle;        
            currentDialValueExact += dialIncrease;

            //Round down if we're above the start value and up if we are below
            setDialValue((int)(currentDialValueExact > dialStartValue ? Math.floor(currentDialValueExact) : Math.ceil(currentDialValueExact));

            //set fingerStartDiff to the current position to allow multiple rounds on the dial
            fingerStartDiffX = xDiff;
            fingerStartDiffY = yDiff;
            break;
    }

    // tell the system that we handled the event and no further processing is required
    return true; 
}

private void setDialValue(int value) {
    //assign value
}

si vous souhaitez changer la direction, il vous suffit de faire alpha = -alpha.

3
répondu Marco de Abreu 2014-03-12 13:26:15

peut-être pourriez-vous regarder dans le onTouchEvent(MotionEvent) de la vue. Garder une trace des coordonnées x et y que vous déplacez le doigt. Remarquez le modèle des changements de coordonnées lorsque vous déplacez le doigt. Vous pouvez utiliser cela pour obtenir l'augmentation/diminution du prix. Voir cette link.

1
répondu Vino 2017-05-23 12:09:06

vous pouvez utiliser le Générateur de gestes Android des échantillons SDK Android.

Je ne peux pas le tester maintenant, mais vous devriez être capable de créer l'application à partir de l'échantillon, l'exécuter, créer les gestes personnalisés que vous voulez (circulaire circulaire dans le sens horaire et dans le sens antihoraire), puis récupérez le fichier brut gestures du périphérique/émulateur de stockage interne (il est créé par l'application après que vous avez fait les gestes).

avec cela, vous pouvez l'importer dans votre projet et utiliser la bibliothèque de gestes pour intercepter, enregistrer et reconnaître les gestes spécifiques. Vous ajoutez essentiellement une disposition de superposition où vous voulez que le geste soit capturé et ensuite vous décidez quoi en faire.

Voir plus en profondeur, guide étape par étape dans le lien suivant: http://www.techotopia.com/index.php/Implementing_Android_Custom_Gesture_and_Pinch_Recognition

1
répondu CodingDuckling 2014-03-12 14:06:11

L'OvalSeekbar lib fait quelque chose comme ça,je suggère que vous ayez un regard sur la façon dont les événements de mouvement sont faits en elle.Voici le lien vers son git https://github.com/kshoji/AndroidCustomViews

1
répondu Niza Siwale 2014-03-12 14:44:30

j'ai écrit ce FrameLayout personnalisé pour détecter le mouvement circulaire autour de son point central. J'utilise l'orientation de trois points sur un plan et l'angle entre eux pour déterminer quand l'Utilisateur a fait un demi-cercle dans une direction et puis le complète dans la même.

public class CircularDialView extends FrameLayout implements OnTouchListener {
    private TextView counter;
    private int count = 50;

    private PointF startTouch;
    private PointF currentTouch;
    private PointF center;
    private boolean turning;
    private boolean switched = false;

    public enum RotationOrientation {
        CW, CCW, LINEAR;
    }
    private RotationOrientation lastRotatationDirection;

    public CircularDialView(Context context) {
        super(context);
        init();
    }

    public CircularDialView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CircularDialView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        this.startTouch = new PointF();
        this.currentTouch = new PointF();
        this.center = new PointF();
        this.turning = false;

        this.setBackgroundResource(R.drawable.dial);
        this.counter = new TextView(getContext());
        this.counter.setTextSize(20);
        FrameLayout.LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        params.gravity = Gravity.CENTER;
        addView(this.counter, params);

        updateCounter();
        this.setOnTouchListener(this);
    }

    private void updateCounter() {
        this.counter.setText(Integer.toString(count));
    }

    // need to keep the view square
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
        center.set(getWidth()/2, getWidth()/2);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                startTouch.set(event.getX(), event.getY());
                turning = true;
                return true; 
            }
            case MotionEvent.ACTION_MOVE: {
                if(turning) {
                    currentTouch.set(event.getX(), event.getY());
                    RotationOrientation turningDirection = getOrientation(center, startTouch, currentTouch);

                    if (lastRotatationDirection != turningDirection) {
                        double angle = getRotationAngle(center, startTouch, currentTouch);
                        Log.d ("Angle", Double.toString(angle));
                        // the touch event has switched its orientation 
                        // and the current touch point is close to the start point
                        // a full cycle has been made
                        if (switched && angle < 10) {
                            if (turningDirection == RotationOrientation.CCW) {
                                count--;
                                updateCounter();
                                switched = false;
                            }
                            else if (turningDirection == RotationOrientation.CW) {
                                count++;
                                updateCounter();
                                switched = false;
                            }
                        }
                        // checking if the angle is big enough is needed to prevent
                        // the user from switching from the start point only
                        else if (!switched && angle > 170) {
                            switched = true;
                        }
                    }

                    lastRotatationDirection = turningDirection;
                    return true;
                }
            }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                turning  = false;
                return true;
            }
        }

        return false;
    }


    // checks the orientation of three points on a plane
    private RotationOrientation getOrientation(PointF a, PointF b, PointF c){
        double face = a.x * b.y + b.x * c.y + c.x * a.y - (c.x * b.y + b.x * a.y + a.x * c.y);
        if (face > 0)
             return RotationOrientation.CW;
        else if (face < 0)
             return RotationOrientation.CCW;
        else return RotationOrientation.LINEAR;
    }

    // using dot product to calculate the angle between the vectors ab and ac
    public double getRotationAngle(PointF a, PointF b, PointF c){
        double len1 = dist (a, b);
        double len2 = dist (a, c);
        double product = (b.x - a.x) * (c.x - a.x) + (b.y - a.y) * (c.y - a.y);

        return Math.toDegrees(Math.acos(product / (len1 * len2)));
    }

    // calculates the distance between two points on a plane
    public double dist (PointF a, PointF b) {
        return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
    }
}
1
répondu SceLus 2014-03-18 14:49:20

j'avais un ami qui avait besoin de mettre en œuvre quelque chose comme ce que vous voulez.

il a en fait utilisé la détection gestuelle - GestureOverlayView et MotionEvent.

en créant ses gestes personnalisés il a réussi à mettre en œuvre ceci.

Mon ami pour la plupart référencés site. Il y a aussi un exemple de code.

j'Espère que vous trouverez ce utile!

0
répondu gilonm 2014-03-12 12:35:37

vous devez utiliser circulaire SeekBar vous pouvez trouver l'échantillon et la librairie de ici

autre peut aussi utile ici , .

merci j'espère que cela aide.

0
répondu Imtiyaz Khalani 2014-03-13 08:40:20

un NumberPicker, qui est plus simple et puis personnaliser il!

NumberPicker

0
répondu Stathis Andronikos 2014-03-19 12:31:38