我该如何实现这样的基于滑动的循环控制?

You*_*sef 19 android swipe

我正在开发一个Android应用程序,我有一个TextView,我显示一个价格(例如50美元).

我想要一个类似于这张图片的循环控件:

在此输入图像描述

  • 在表盘上顺时针滑动手指会使金额增加1美元
  • 在表盘上逆时针滑动手指可减少$ 1步

我做了一些研究,但找不到有效的实现方法.

你怎么能创建这种由滑动驱动的循环控制?

ale*_*lex 16

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

}
Run Code Online (Sandbox Code Playgroud)

用它 :

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

}
Run Code Online (Sandbox Code Playgroud)

结果:

结果

  • 没问题,我也需要它,但在我的情况下,有超大的触摸启动区.我分享了我的工作,也许其他人会感兴趣 (6认同)

May*_*tro 9

我已经修改了circularseekbar的源代码以便按你的意愿工作.

你可以从修改过的cirucularseekbar获得mofidied类

首先在布局中包含控件并将拨盘设置为背景

            <com.yourapp.CircularSeekBar
                android:id="@+id/circularSeekBar"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/amount_wheel_bg" />
Run Code Online (Sandbox Code Playgroud)

然后,在您的活动中(它应该实现OnCircularSeekBarChangeListener)添加以下内容:

//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;
Run Code Online (Sandbox Code Playgroud)

并添加以下回调方法:

@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));
   }
Run Code Online (Sandbox Code Playgroud)

selectedAmount是用于存储所选金额的double属性.

我希望这可以帮到你.

  • 破碎的Dropbox链接 (2认同)