Как я могу реализовать круговое управление на основе swipe, как это?
Я работаю над Android-приложением, и у меня есть TextView, где я показываю цену (например, 50 $).
Я хотел бы иметь круговой контроль, подобный этому изображению:
![enter image description here]()
- Прокрутка пальца по часовой стрелке на циферблате увеличивает количество на $1 шагов
- Прокрутка пальца против часовой стрелки на циферблате уменьшает количество
на $1 шагов
Я провел некоторое исследование, но не смог найти что-то для этого.
Как вы могли создать такое круговое управление, управляемое swipes?
Ответы
Ответ 1
Я изменил источник circularseekbar, чтобы работать так, как вы хотите.
Вы можете получить mofidied класс из измененного cirucularseekbar
Сначала Включите элемент управления в свой макет и установите свой циферблат в качестве фона
<com.yourapp.CircularSeekBar
android:id="@+id/circularSeekBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/amount_wheel_bg" />
Затем в вашей деятельности (он должен реализовать 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;
И добавьте следующие методы обратного вызова:
@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 является двойным свойством для хранения выбранного количества.
Надеюсь, это поможет вам.
Ответ 2
Класс 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);
}
Используйте его:
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);
}
}
Результат:
![result]()
Ответ 3
Я только что написал следующий код и теоретически тестировал его.
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
}
Если вы хотите изменить направление, просто сделайте alpha = -alpha
.
Ответ 4
Возможно, вы могли бы взглянуть на onTouchEvent(MotionEvent)
представления. Следите за координатами x и y при перемещении пальца. Обратите внимание, что шаблон изменения координат изменяется при перемещении пальца. Вы можете использовать это для достижения увеличения/снижения цены. Смотрите link
.
Ответ 5
Вы можете использовать Android Gesture Builder из образцов Android SDK.
Я не могу проверить его прямо сейчас, но вы должны иметь возможность создавать приложение из образца, запускать его, создавать настраиваемые жесты (круговые по часовой стрелке и круглые против часовой стрелки), а затем получать сырые файлы жестов из внутреннего хранилища устройства/эмулятора (он создается приложением после того, как вы делаете жесты).
При этом вы можете импортировать его в свой проект и использовать библиотеку жестов для перехвата, регистрации и распознавания конкретных жестов. Вы в основном добавляете оверлейный макет, где хотите, чтобы жест был захвачен, а затем вы решаете, что с ним делать.
Подробное описание пошагово, пошаговое руководство по следующей ссылке: http://www.techotopia.com/index.php/Implementing_Android_Custom_Gesture_and_Pinch_Recognition
Ответ 6
OvalSeekbar lib делает что-то подобное, я предлагаю вам посмотреть, как в нем происходят события движения. Вот ссылка на его git https://github.com/kshoji/AndroidCustomViews
Ответ 7
Я написал этот пользовательский FrameLayout, чтобы обнаружить круговое движение вокруг своей центральной точки.
Я использую ориентацию трех точек на плоскости и угол между ними, чтобы определить, когда пользователь сделал половину круга в одном направлении, а затем завершает его в том же самом.
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));
}
}
Ответ 8
У меня был друг, которому нужно было реализовать нечто похожее, как то, что вы хотите.
Он фактически использовал обнаружение жестов - GestureOverlayView
и MotionEvent
.
Создав свои собственные жесты, он смог реализовать это.
Мой друг в основном ссылался на этот сайт. Здесь есть пример кода.
Надеюсь, вы найдете это полезным!
Ответ 9
вам нужно использовать Circular SeekBar, вы можете найти образец и библиотеку из здесь
какой-то другой также может быть полезен здесь, this.
thanx я надеюсь, что это поможет.
Ответ 10
Используйте NumberPicker, который проще, а затем настройте его!
![NumberPicker]()
Ответ 11
У меня есть набор пользовательских функций набора номера, который делает именно то, что вам нужно. Здесь вы можете найти репозиторий: https://bitbucket.org/warwick/hgdialrepo
В репозитории есть демо APK, который вы также можете загрузить из Google Play Store. Просто выполните поиск в Google Play для HGDial.
Элемент управления также имеет версию 2, которую вы можете найти здесь: https://bitbucket.org/warwick/hg_dial_v2