我已经创建了一个自定义范围搜索栏,我想为其实现可访问性。在默认搜索栏中,将球向右/向上移动的正常手势是“先右后左”。因此,当用户将注意力集中在左球上时,我想在对讲功能打开时检测“先右后左”手势,然后将球移动一步。
您可以激活对讲并使用音量作为我要达到的目标的示例。
对话手势信息: URL
public class MyRangeSeekBar extends ConstraintLayout implements View.OnTouchListener {
private static final String TAG = MyRangeSeekBar.class.getSimpleName();
private static final long BALL_SPEED = 300;
private static final int STEPS_LIMIT = 2;
private static final int STEPS_MAX = 10;
private final MyRangeSeekBarBinding binding;
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH);
private int min = 1;
private int max = 10;
private int currentMin = 1;
private int currentMax = 10;
private float dX;
private float posInitBar;
private float posEndBar;
private float halfBallWidth;
private float barWidth;
private float segmentWidth;
private int rangeDescriptionLabel;
private int rangeJoinDescriptionLabel;
private int minimumDescriptionLabel;
private int maximumDescriptionLabel;
private DecimalFormat df = new DecimalFormat("#.##", symbols);
public MyRangeSeekBar(Context context) {
this(context, null);
}
public MyRangeSeekBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyRangeSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
binding = MyRangeSeekBarBinding.inflate(inflater, this, true);
initAttrs(context, attrs, defStyleAttr);
}
private void initAttrs(Context context, AttributeSet attrs, int defStyleAttr) {
final TypedArray attributes =
context.obtainStyledAttributes(
attrs,
R.styleable.MyRangeSeekBar,
defStyleAttr,
0);
min = attributes.getInt(R.styleable.MyRangeSeekBar_min, 1);
max = attributes.getInt(R.styleable.MyRangeSeekBar_max, 10);
currentMin = attributes.getInt(R.styleable.MyRangeSeekBar_currentMin, 1);
currentMax = attributes.getInt(R.styleable.MyRangeSeekBar_currentMax, 10);
rangeDescriptionLabel = attributes.getResourceId(R.styleable.MyRangeSeekBar_rangeDescriptionLabel, R.string.mcaiucomps_range_label);
rangeJoinDescriptionLabel = attributes.getResourceId(R.styleable.MyRangeSeekBar_rangeJoinDescriptionLabel, R.string.mcaiucomps_range_join_label);
minimumDescriptionLabel = attributes.getResourceId(R.styleable.MyRangeSeekBar_minimumDescriptionLabel, R.string.mcaiucomps_min_label);
maximumDescriptionLabel = attributes.getResourceId(R.styleable.MyRangeSeekBar_maximumDescriptionLabel, R.string.mcaiucomps_max_label);
attributes.recycle();
refresh();
}
private void checkValues() {
if (max - min < STEPS_LIMIT) {
throw new RuntimeException("Not enough steps, you need a minimum of 2 steps like from 1 to 3");
}
if (max - min >= STEPS_MAX) {
throw new RuntimeException("Too many steps, you can't do more than 10 steps");
}
if (currentMin < min || currentMin > max) {
throw new RuntimeException("The minimum value is not between the limits");
}
if (currentMax < min || currentMax > max) {
throw new RuntimeException("The maximum value is not between the limits");
}
if (currentMin > currentMax) {
throw new RuntimeException("The minimum is bigger than the maximum value");
}
}
private void setBar() {
binding.mcaiucompsBar.post(new Runnable() {
@Override
public void run() {
halfBallWidth = binding.mcaiucompsBallLeft.getMeasuredWidth() / 2;
posInitBar = binding.mcaiucompsBar.getX() + halfBallWidth;
posEndBar = binding.mcaiucompsBar.getMeasuredWidth() - halfBallWidth;
binding.mcaiucompsBallRight.setX(posEndBar + halfBallWidth);
binding.mcaiucompsBallLeft.setX(posInitBar + halfBallWidth);
barWidth = posEndBar - posInitBar;
segmentWidth = barWidth / (max - min);
setBalls();
}
});
}
private void setTitles() {
binding.mcaiucompsMinValue.setText(getContext().getString(minimumDescriptionLabel, min));
binding.mcaiucompsMinValue.setContentDescription(getContext().getString(minimumDescriptionLabel, min) + ", " + getContext().getString(R.string.caixabank_componente_rango_minimo));
binding.mcaiucompsMaxValue.setText(getContext().getString(maximumDescriptionLabel, max));
binding.mcaiucompsMaxValue.setContentDescription(getContext().getString(maximumDescriptionLabel, max) + ", " + getContext().getString(R.string.caixabank_componente_rango_maximo));
binding.mcaiucompsTitle.setText(getContext().getString(rangeDescriptionLabel, currentMin, currentMax));
binding.mcaiucompsTitle.setContentDescription(getContext().getString(rangeDescriptionLabel, currentMin, currentMax));
}
private void updateBallsPosition(View view) {
//Check limits
if (binding.mcaiucompsBallLeft.getId() == view.getId() && getLeftBallPos() < posInitBar) {
animateBall(view, posInitBar);
} else if (binding.mcaiucompsBallRight.getId() == view.getId() && getRightBallPos() > posEndBar) {
animateBall(view, posEndBar);
} else {
//Move to the closest position
if (view.getId() == binding.mcaiucompsBallLeft.getId()) {
if (getLeftBallPos() >= posInitBar) {
currentMin = Math.round(getLeftBallPos() / segmentWidth);
} else if (getLeftBallPos() >= getRightBallPos()) {
currentMin = currentMax;
} else {
currentMin = min;
}
animateBall(binding.mcaiucompsBallLeft, currentMin * segmentWidth + posInitBar);
} else {
if (getRightBallPos() < barWidth) {
currentMax = Math.round(getRightBallPos() / segmentWidth);
} else if (getRightBallPos() >= getLeftBallPos()) {
currentMax = currentMin;
} else {
currentMax = max - min;
}
animateBall(binding.mcaiucompsBallRight, currentMax * segmentWidth + posInitBar);
}
}
}
private void animateBall(View view, float pos) {
pos = Float.parseFloat(df.format(pos));
// Leave some space between balls if they are in the same position
if (view.getId() == binding.mcaiucompsBallLeft.getId()
&& (pos <= getRightBallPos() && pos >= getRightBallPos() - halfBallWidth || pos >= posEndBar)) {
binding.mcaiucompsBallLeft.animate()
.x(getRightBallPos() - halfBallWidth * 1.7f)
.setDuration(BALL_SPEED)
.setInterpolator(new DecelerateInterpolator())
.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
changeBarHighlightSize();
updateTitle();
}
})
.start();
} else if (view.getId() == binding.mcaiucompsBallRight.getId()
&& (pos >= getLeftBallPos() && pos <= getLeftBallPos() + halfBallWidth || pos <= posInitBar)) {
binding.mcaiucompsBallRight.animate()
.x(getLeftBallPos() - halfBallWidth * 0.3f)
.setDuration(BALL_SPEED)
.setInterpolator(new DecelerateInterpolator())
.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
changeBarHighlightSize();
updateTitle();
}
})
.start();
} else {
view.animate()
.x(pos - halfBallWidth)
.setDuration(BALL_SPEED)
.setInterpolator(new DecelerateInterpolator())
.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
changeBarHighlightSize();
updateTitle();
}
})
.start();
}
}
private void updateTitle() {
int minLabel = Math.round((getLeftBallPos() - posInitBar) / segmentWidth) + min;
int maxLabel = Math.round((getRightBallPos() - posInitBar) / segmentWidth) + min;
if (getRightBallPos() == posEndBar || maxLabel > max) {
maxLabel = max;
}
if (getLeftBallPos() == posInitBar || minLabel < min) {
minLabel = min;
}
String title = getContext().getString(rangeDescriptionLabel, minLabel, maxLabel);
if (minLabel == maxLabel) {
title = getContext().getString(rangeJoinDescriptionLabel, minLabel);
}
binding.mcaiucompsTitle.setText(title);
binding.mcaiucompsTitle.setContentDescription(title);
}
private float getLeftBallPos() {
return Float.parseFloat(df.format(binding.mcaiucompsBallLeft.getX() + halfBallWidth));
}
private float getRightBallPos() {
return Float.parseFloat(df.format(binding.mcaiucompsBallRight.getX() + halfBallWidth));
}
private void setBalls() {
binding.mcaiucompsBallLeft.setOnTouchListener(this);
binding.mcaiucompsBallRight.setOnTouchListener(this);
float posMin = (currentMin - min) * segmentWidth + posInitBar;
float posMax = (currentMax - min) * segmentWidth + posInitBar;
if (currentMax == currentMin) {
posMin = posMax + halfBallWidth - halfBallWidth * 1.7f;
}
animateBall(binding.mcaiucompsBallLeft, posMin);
animateBall(binding.mcaiucompsBallRight, posMax);
}
@Override
public boolean onTouch(View view, MotionEvent event) {
if (binding.mcaiucompsBallLeft.getId() == view.getId()
|| binding.mcaiucompsBallRight.getId() == view.getId()) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
dX = view.getX() - event.getRawX();
view.bringToFront();
break;
case MotionEvent.ACTION_MOVE:
if (isValidMove(view, event.getRawX() + dX)) {
view.animate()
.x(event.getRawX() + dX)
.setDuration(0)
.start();
changeBarHighlightSize();
updateTitle();
Log.d(TAG, "onTouch: " + event.getRawX() + " dx: " + dX);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
updateBallsPosition(view);
break;
default:
return false;
}
return true;
}
return false;
}
private boolean isValidMove(View view, float rawX) {
if ((binding.mcaiucompsBallLeft.getId() == view.getId() && getLeftBallPos() >= posInitBar)
|| (binding.mcaiucompsBallRight.getId() == view.getId() && getRightBallPos() <= posEndBar)) {
if ((binding.mcaiucompsBallLeft.getId() == view.getId() && rawX + halfBallWidth / 2 <= binding.mcaiucompsBallRight.getX())
|| (binding.mcaiucompsBallRight.getId() == view.getId() && rawX + halfBallWidth / 2 >= getLeftBallPos())) {
return true;
}
}
return false;
}
public void changeBarHighlightSize() {
ViewGroup.LayoutParams params = binding.mcaiucompsBarHighlight.getLayoutParams();
params.width = (int) (getRightBallPos() - getLeftBallPos());
binding.mcaiucompsBarHighlight.setLayoutParams(params);
binding.mcaiucompsBarHighlight.setX(getLeftBallPos());
}
public int getMin() {
return min;
}
public void setMin(int min) {
this.min = min;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public int getCurrentMin() {
return currentMin;
}
public void setCurrentMin(int currentMin) {
this.currentMin = currentMin;
}
public int getCurrentMax() {
return currentMax;
}
public void setCurrentMax(int currentMax) {
this.currentMax = currentMax;
}
public void setRangeDescriptionLabel(@StringRes int rangeDescriptionLabel) {
this.rangeDescriptionLabel = rangeDescriptionLabel;
updateTitle();
}
public void setRangeJoinDescriptionLabel(@StringRes int rangeJoinDescriptionLabel) {
this.rangeJoinDescriptionLabel = rangeJoinDescriptionLabel;
updateTitle();
}
public void setMinimumDescriptionLabel(@StringRes int minimumDescriptionLabel) {
this.minimumDescriptionLabel = minimumDescriptionLabel;
setTitles();
}
public void setMaximumDescriptionLabel(@StringRes int maximumDescriptionLabel) {
this.maximumDescriptionLabel = maximumDescriptionLabel;
setTitles();
}
public void refresh() {
checkValues();
setBar();
setTitles();
}
@Override
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
return super.onRequestSendAccessibilityEvent(child, event);
}
}
谢谢!