¿Cómo implementaría un control circular basado en el swipe como este?

Estoy trabajando en una aplicación de Android, y tengo un TextView donde muestro un precio (por ejemplo 50 $).

Me gustaría tener un control circular similar a esta imagen:

Introduzca aquí la descripción de la imagen

  • Si desliza un dedo en el sentido de las agujas del reloj en el dial, aumenta la cantidad en pasos de $ 1
  • Si desliza un dedo en sentido contrario a las agujas del reloj en el dial disminuye la cantidad en pasos de $ 1

Hice algunas investigaciones, pero no pude encontrar una implementación de trabajo de algo para hacer esto.

¿Cómo podrías crear un control circular como éste, impulsado por golpes?

He modificado la fuente de circularseekbar para que funcione como quieras.

Puedes obtener la clase mofidied modificada cirucularseekbar

Primero Incluya el control en su diseño y configure su marcación como fondo

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

A continuación, en su actividad (debe implementar OnCircularSeekBarChangeListener) agregue lo siguiente:

 //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; 

Y agregue los siguientes métodos de devolución de llamada:

 @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 es una propiedad doble para almacenar la cantidad seleccionada.

Espero que esto pueda ayudarte.

Clase 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); } 

Utilícelo:

 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); } } 

Resultado:

resultado

Acabo de escribir el siguiente código y sólo lo probé teóricamente.

 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 desea cambiar la dirección, simplemente haga alpha = -alpha .

Tal vez usted podría mirar en el onTouchEvent(MotionEvent) de la vista. Mantenga una pista de las coordenadas x e y mientras mueve el dedo. Observe el patrón de los cambios de coordenadas a medida que mueve el dedo. Usted puede utilizar eso para lograr el aumento / disminución en el precio. Vea este link .

Puede utilizar Android Gesture Builder de las muestras de Android SDK.

No puedo probarlo ahora mismo, pero debería ser capaz de crear la aplicación de la muestra, ejecutarla, crear los gestos personalizados que desee ( circular en el sentido de las agujas del reloj y circular en sentido contrario a las agujas del reloj ), y luego obtener los gestos archivo raw desde el dispositivo / Emulador de almacenamiento interno (que es creado por la aplicación después de hacer los gestos).

Con eso, puede importarlo a su proyecto y utilizar la Gesture Library para interceptar, registrar y reconocer los gestos específicos. Básicamente, agrega un diseño de superposición en el que desea que el gesto se capture y, a continuación, decide qué hacer con él.

Vea más en profundidad, guía paso a paso en el siguiente enlace: http://www.techotopia.com/index.php/Implementing_Android_Custom_Gesture_and_Pinch_Recognition

El OvalSeekbar lib hace algo así, sugiero que eche un vistazo a cómo se realizan los eventos de movimiento en él. Aquí está el enlace a su git https://github.com/kshoji/AndroidCustomViews

Escribí este FrameLayout personalizado para detectar movimiento circular alrededor de su punto central. Estoy usando la orientación de tres puntos en un plano y el ángulo entre ellos para determinar cuando el usuario ha hecho la mitad de un círculo en una dirección y luego lo completa en la misma.

 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 = ax * by + bx * cy + cx * ay - (cx * by + bx * ay + ax * cy); 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 = (bx - ax) * (cx - ax) + (by - ay) * (cy - ay); 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((ax - bx) * (ax - bx) + (ay - by) * (ay - by)); } } 

Tuve un amigo que necesitaba implementar algo similar como lo que quieres.

En realidad utilizó la detección de gestos – GestureOverlayView y MotionEvent .

Mediante la creación de sus gestos personalizados que logró aplicar esto.

Mi amigo en su mayoría referenciado este sitio. Hay un código de ejemplo allí también.

¡Espero que encuentres esto útil!

Usted necesita utilizar Circular SeekBar usted puede encontrar muestra y librery de aquí

Algunos otros también pueden útil aquí , esto .

Thanx espero que ayude.

Utilice un NumberPicker , que es más simple y luego personalizarlo!

Número de pelea

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.