Android - кнопка удерживания, чтобы повторить действие
-
28-09-2019 - |
Вопрос
Утро все,
Я признаю прямо сейчас, что я новичок в развитии и пробую руку на андроиде. Я пытался искать «NET, чтобы найти совет о том, как реализовать некоторые» кнопку «Удерживать», чтобы повторить действие «- я создал пользовательский numpad с кнопок и хочу подобное подобное расстоянию. Должен до сих пор, я позвонил другу, который раньше был закодирован Android, но сделал много C # / Java и, кажется, знает, что он делает.
Код ниже работает просто хорошо, но я чувствую, что это может быть сделано более аккуратно. Я прошу прощения, если я пропустил биты, но, надеюсь, это объясняет мой подход. Я думаю, что OnTouchListener в порядке, но способ обрабатывается потоки, не чувствуют себя правильно.
Есть ли лучший или более простой способ сделать это?
Спасибо,
М.
public class MyApp extends Activity {
private boolean deleteThreadRunning = false;
private boolean cancelDeleteThread = false;
private Handler handler = new Handler();
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
//May have missed some declarations here...
Button_Del.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
handleDeleteDown();
return true;
}
case MotionEvent.ACTION_UP:
{
handleDeleteUp();
return true;
}
default:
return false;
}
}
private void handleDeleteDown() {
if (!deleteThreadRunning)
startDeleteThread();
}
private void startDeleteThread() {
Thread r = new Thread() {
@Override
public void run() {
try {
deleteThreadRunning = true;
while (!cancelDeleteThread) {
handler.post(new Runnable() {
@Override
public void run() {
deleteOneChar();
}
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(
"Could not wait between char delete.", e);
}
}
}
finally
{
deleteThreadRunning = false;
cancelDeleteThread = false;
}
}
};
// actually start the delete char thread
r.start();
}
});
}
private void handleDeleteUp() {
cancelDeleteThread = true;
}
private void deleteOneChar()
{
String result = getNumberInput().getText().toString();
int Length = result.length();
if (Length > 0)
getNumberInput().setText(result.substring(0, Length-1));
//I've not pasted getNumberInput(), but it gets the string I wish to delete chars from
}
Решение
Это более независимая реализация, пригодная для использования с любым представлением, что поддерживает сенсорное событие:
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
/**
* A class, that can be used as a TouchListener on any view (e.g. a Button).
* It cyclically runs a clickListener, emulating keyboard-like behaviour. First
* click is fired immediately, next one after the initialInterval, and subsequent
* ones after the normalInterval.
*
* <p>Interval is scheduled after the onClick completes, so it has to run fast.
* If it runs slow, it does not generate skipped onClicks. Can be rewritten to
* achieve this.
*/
public class RepeatListener implements OnTouchListener {
private Handler handler = new Handler();
private int initialInterval;
private final int normalInterval;
private final OnClickListener clickListener;
private View touchedView;
private Runnable handlerRunnable = new Runnable() {
@Override
public void run() {
if(touchedView.isEnabled()) {
handler.postDelayed(this, normalInterval);
clickListener.onClick(touchedView);
} else {
// if the view was disabled by the clickListener, remove the callback
handler.removeCallbacks(handlerRunnable);
touchedView.setPressed(false);
touchedView = null;
}
}
};
/**
* @param initialInterval The interval after first click event
* @param normalInterval The interval after second and subsequent click
* events
* @param clickListener The OnClickListener, that will be called
* periodically
*/
public RepeatListener(int initialInterval, int normalInterval,
OnClickListener clickListener) {
if (clickListener == null)
throw new IllegalArgumentException("null runnable");
if (initialInterval < 0 || normalInterval < 0)
throw new IllegalArgumentException("negative interval");
this.initialInterval = initialInterval;
this.normalInterval = normalInterval;
this.clickListener = clickListener;
}
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.removeCallbacks(handlerRunnable);
handler.postDelayed(handlerRunnable, initialInterval);
touchedView = view;
touchedView.setPressed(true);
clickListener.onClick(view);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handler.removeCallbacks(handlerRunnable);
touchedView.setPressed(false);
touchedView = null;
return true;
}
return false;
}
}
Использование:
Button button = new Button(context);
button.setOnTouchListener(new RepeatListener(400, 100, new OnClickListener() {
@Override
public void onClick(View view) {
// the code to execute repeatedly
}
}));
Другие советы
Вот простой класс под названием AutorePeatButton, который может во многих случаях использоваться в качестве замены в качестве заправки для стандартного класса кнопок:
package com.yourdomain.yourlibrary;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
public class AutoRepeatButton extends Button {
private long initialRepeatDelay = 500;
private long repeatIntervalInMilliseconds = 100;
private Runnable repeatClickWhileButtonHeldRunnable = new Runnable() {
@Override
public void run() {
//Perform the present repetition of the click action provided by the user
// in setOnClickListener().
performClick();
//Schedule the next repetitions of the click action, using a faster repeat
// interval than the initial repeat delay interval.
postDelayed(repeatClickWhileButtonHeldRunnable, repeatIntervalInMilliseconds);
}
};
private void commonConstructorCode() {
this.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN)
{
//Just to be sure that we removed all callbacks,
// which should have occurred in the ACTION_UP
removeCallbacks(repeatClickWhileButtonHeldRunnable);
//Perform the default click action.
performClick();
//Schedule the start of repetitions after a one half second delay.
postDelayed(repeatClickWhileButtonHeldRunnable, initialRepeatDelay);
}
else if(action == MotionEvent.ACTION_UP) {
//Cancel any repetition in progress.
removeCallbacks(repeatClickWhileButtonHeldRunnable);
}
//Returning true here prevents performClick() from getting called
// in the usual manner, which would be redundant, given that we are
// already calling it above.
return true;
}
});
}
public AutoRepeatButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
commonConstructorCode();
}
public AutoRepeatButton(Context context, AttributeSet attrs) {
super(context, attrs);
commonConstructorCode();
}
public AutoRepeatButton(Context context) {
super(context);
commonConstructorCode();
}
}
Ваша базовая реализация - это звук. Тем не менее, я бы показал, что логику в другой класс, чтобы вы могли использовать его в других местах без дублирования кода. Увидеть, например, это Реализация класса «RepeatListener», который делает то же самое, что вы хотите сделать, за исключением бара ищет.
Вот Другая нить с альтернативным решением, но это очень похоже на ваш первый.
RepuretListenlistenercass Довольно хорошо, но он не обрабатывает «motionevent.cong_cancel», поэтому обработчик не удаляет перезвонить в это действие. Это делает проблемы в PagerAdapter., и так далее. Поэтому я добавил этот случай события.
private Rect rect; // Variable rect to hold the bounds of the view
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.removeCallbacks(handlerRunnable);
handler.postDelayed(handlerRunnable, initialInterval);
downView = view;
rect = new Rect(view.getLeft(), view.getTop(), view.getRight(),
view.getBottom());
clickListener.onClick(view);
break;
case MotionEvent.ACTION_UP:
handler.removeCallbacks(handlerRunnable);
downView = null;
break;
case MotionEvent.ACTION_MOVE:
if (!rect.contains(view.getLeft() + (int) motionEvent.getX(),
view.getTop() + (int) motionEvent.getY())) {
// User moved outside bounds
handler.removeCallbacks(handlerRunnable);
downView = null;
Log.d(TAG, "ACTION_MOVE...OUTSIDE");
}
break;
case MotionEvent.ACTION_CANCEL:
handler.removeCallbacks(handlerRunnable);
downView = null;
break;
}
return false;
}
Класс Carl - это самостоятельно и работает нормально.
Я бы сделал начальную задержку и повторять интервал настраиваемый. Сделать это,
Abtri.xml.
<resources>
<declare-styleable name="AutoRepeatButton">
<attr name="initial_delay" format="integer" />
<attr name="repeat_interval" format="integer" />
</declare-styleable>
</resources>
Autorepeatbutton.java
public AutoRepeatButton(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoRepeatButton);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.AutoRepeatButton_initial_delay:
initialRepeatDelay = a.getInt(attr, DEFAULT_INITIAL_DELAY);
break;
case R.styleable.AutoRepeatButton_repeat_interval:
repeatIntervalInMilliseconds = a.getInt(attr, DEFAULT_REPEAT_INTERVAL);
break;
}
}
a.recycle();
commonConstructorCode();
}
Тогда вы можете использовать такой класс
<com.thepath.AutoRepeatButton
xmlns:repeat="http://schemas.android.com/apk/res/com.thepath"
android:id="@+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/selector_btn_delete"
android:onClick="onBtnClick"
android:layout_weight="1"
android:layout_margin="2dp"
repeat:initial_delay="1500"
repeat:repeat_interval="150"
/>
Вот ответ, основанный на Оливе со следующими настройками:
- Вместо того, чтобы прослушать прослушиватель и звонить
onClick
напрямую, это называетperformClick
илиperformLongClick
на вид. Это будет вызвать стандартное нажатие на поведение, например, Haptic обратной связи в длинном щелчке. - Его можно настроить, чтобы либо пожар
onClick
немедленно (как оригинал) или только наACTION_UP
И только если события кликов не выдержали (больше похоже на насколько стандартнымonClick
работает). - Альтернативный конструктор No-Arg, который устанавливает
immediateClick
к ложе и использует системный стандарт Длительное нажатие тайм-аут для обоих интервалов. Для меня это чувствует себя наиболее похоже на то, что будет стандартным «повторным длинным прессом», если оно существовало.
Вот:
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
/**
* A class that can be used as a TouchListener on any view (e.g. a Button).
* It either calls performClick once, or performLongClick repeatedly on an interval.
* The performClick can be fired either immediately or on ACTION_UP if no clicks have
* fired. The performLongClick is fired once after initialInterval and then repeatedly
* after normalInterval.
*
* <p>Interval is scheduled after the onClick completes, so it has to run fast.
* If it runs slow, it does not generate skipped onClicks.
*
* Based on http://stackoverflow.com/a/12795551/642160
*/
public class RepeatListener implements OnTouchListener {
private Handler handler = new Handler();
private final boolean immediateClick;
private final int initialInterval;
private final int normalInterval;
private boolean haveClicked;
private Runnable handlerRunnable = new Runnable() {
@Override
public void run() {
haveClicked = true;
handler.postDelayed(this, normalInterval);
downView.performLongClick();
}
};
private View downView;
/**
* @param immediateClick Whether to call onClick immediately, or only on ACTION_UP
* @param initialInterval The interval after first click event
* @param normalInterval The interval after second and subsequent click
* events
* @param clickListener The OnClickListener, that will be called
* periodically
*/
public RepeatListener(
boolean immediateClick,
int initialInterval,
int normalInterval)
{
if (initialInterval < 0 || normalInterval < 0)
throw new IllegalArgumentException("negative interval");
this.immediateClick = immediateClick;
this.initialInterval = initialInterval;
this.normalInterval = normalInterval;
}
/**
* Constructs a repeat-listener with the system standard long press time
* for both intervals, and no immediate click.
*/
public RepeatListener()
{
immediateClick = false;
initialInterval = android.view.ViewConfiguration.getLongPressTimeout();
normalInterval = initialInterval;
}
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.removeCallbacks(handlerRunnable);
handler.postDelayed(handlerRunnable, initialInterval);
downView = view;
if (immediateClick)
downView.performClick();
haveClicked = immediateClick;
return true;
case MotionEvent.ACTION_UP:
// If we haven't clicked yet, click now
if (!haveClicked)
downView.performClick();
// Fall through
case MotionEvent.ACTION_CANCEL:
handler.removeCallbacks(handlerRunnable);
downView = null;
return true;
}
return false;
}
}
Класс Карла Это довольно хорошо, вот модификация, которая позволит ускорению (чем дольше вы удерживаете более быструю функцию Щелки:
package com.yourdomain.yourlibrary;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
public class AutoRepeatButton extends Button {
private long initialRepeatDelay = 500;
private long repeatIntervalInMilliseconds = 100;
// speedup
private long repeatIntervalCurrent = repeatIntervalInMilliseconds;
private long repeatIntervalStep = 2;
private long repeatIntervalMin = 10;
private Runnable repeatClickWhileButtonHeldRunnable = new Runnable() {
@Override
public void run() {
// Perform the present repetition of the click action provided by the user
// in setOnClickListener().
performClick();
// Schedule the next repetitions of the click action,
// faster and faster until it reaches repeaterIntervalMin
if (repeatIntervalCurrent > repeatIntervalMin)
repeatIntervalCurrent = repeatIntervalCurrent - repeatIntervalStep;
postDelayed(repeatClickWhileButtonHeldRunnable, repeatIntervalCurrent);
}
};
private void commonConstructorCode() {
this.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
// Just to be sure that we removed all callbacks,
// which should have occurred in the ACTION_UP
removeCallbacks(repeatClickWhileButtonHeldRunnable);
// Perform the default click action.
performClick();
// Schedule the start of repetitions after a one half second delay.
repeatIntervalCurrent = repeatIntervalInMilliseconds;
postDelayed(repeatClickWhileButtonHeldRunnable, initialRepeatDelay);
} else if (action == MotionEvent.ACTION_UP) {
// Cancel any repetition in progress.
removeCallbacks(repeatClickWhileButtonHeldRunnable);
}
// Returning true here prevents performClick() from getting called
// in the usual manner, which would be redundant, given that we are
// already calling it above.
return true;
}
});
}
public AutoRepeatButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
commonConstructorCode();
}
public AutoRepeatButton(Context context, AttributeSet attrs) {
super(context, attrs);
commonConstructorCode();
}
public AutoRepeatButton(Context context) {
super(context);
commonConstructorCode();
}
}
Класс Карла хорош для меня. Но это некоторая проблема при нажатии кнопки и перетаскивания. В случае выхода из области кнопки, все еще продолжаясь на событии Click.
Пожалуйста, добавьте код о Action_Move вроде как « Android: обнаружить, если пользователь касается и перетаскивает из области кнопки?'
Вот немного разного решения, не используя вложенный щелчок слушателя.
Использование:
view.setOnTouchListener(new LongTouchIntervalListener(1000) {
@Override
public void onTouchInterval() {
// do whatever you want
}
});
И сам слушатель:
public abstract class LongTouchIntervalListener implements View.OnTouchListener {
private final long touchIntervalMills;
private long touchTime;
private Handler handler = new Handler();
public LongTouchIntervalListener(final long touchIntervalMills) {
if (touchIntervalMills <= 0) {
throw new IllegalArgumentException("Touch touch interval must be more than zero");
}
this.touchIntervalMills = touchIntervalMills;
}
public abstract void onTouchInterval();
@Override
public boolean onTouch(final View v, final MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
onTouchInterval();
touchTime = System.currentTimeMillis();
handler.postDelayed(touchInterval, touchIntervalMills);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
touchTime = 0;
handler.removeCallbacks(touchInterval);
return true;
default:
break;
}
return false;
}
private final Runnable touchInterval = new Runnable() {
@Override
public void run() {
onTouchInterval();
if (touchTime > 0) {
handler.postDelayed(this, touchIntervalMills);
}
}
};
}