当按钮被按下时,连续增加整数值

38

我是Android的新手,如果问题很简单,请见谅。我有两个按钮,一个减少按钮和一个增加按钮,在它们中间有一个TextView显示一个值。

当我点击减少按钮时,TextView中的值减少,点击增加按钮时则增加,这方面没有问题,我已经实现了。但是问题是,只有在单击一次时值才会增加/减少1。我想实现的是,当我持续按下按钮(例如增加按钮)时,值也会持续增加,并且只有在我松开增加按钮时才停止增加。

这种可能吗?如果可以的话,你能展示一些实现方法的示例代码或参考资料吗?谢谢!

这是我的main.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center" >

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="44dp"
        android:gravity="center_horizontal" >

        <Button android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:text="&lt;" />

        <TextView android:id="@+id/textView1"
            android:layout_width="50dp"
            android:layout_height="fill_parent"
            android:layout_alignBottom="@+id/button1"
            android:layout_toRightOf="@+id/button1"
            android:gravity="center"
            android:text="45" />

        <Button android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_toRightOf="@+id/textView1"
            android:text="&gt;" />

     </RelativeLayout>   

</RelativeLayout>

这是我的Main.java文件

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class Main extends Activity {

    private Button _decrease;
    private Button _increase;
    private TextView _value;
    private static int _counter = 45;
    private String _stringVal;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        _decrease = (Button) findViewById(R.id.button1);
        _increase = (Button) findViewById(R.id.button2);
        _value = (TextView) findViewById(R.id.textView1);

        _decrease.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                Log.d("src", "Decreasing value...");
                _counter--;
                _stringVal = Integer.toString(_counter);
                _value.setText(_stringVal);
            }
        });

        _increase.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                Log.d("src", "Increasing value...");
                _counter++;
                _stringVal = Integer.toString(_counter);
                _value.setText(_stringVal);
            }
        });

    }
}
12个回答

92

为了实现这个功能,您需要一个线程,在长按按钮时更新整数值。

在您的活动中创建一个处理程序:

private Handler repeatUpdateHandler = new Handler();

还有两个变量,它们将说明:是增加还是减少?一次只设置一个。

private boolean mAutoIncrement = false;
private boolean mAutoDecrement = false;

和当前的数字值

public int mValue;

还有一个在另一个线程中运行的类:

class RptUpdater implements Runnable {
    public void run() {
        if( mAutoIncrement ){
            increment();
            repeatUpdateHandler.postDelayed( new RptUpdater(), REP_DELAY );
        } else if( mAutoDecrement ){
            decrement();
            repeatUpdateHandler.postDelayed( new RptUpdater(), REP_DELAY );
        }
    }
}

为您的按钮添加长按监听器:

mBTIncrement.setOnLongClickListener( 
            new View.OnLongClickListener(){
                public boolean onLongClick(View arg0) {
                    mAutoIncrement = true;
                    repeatUpdateHandler.post( new RptUpdater() );
                    return false;
                }
            }
    );   

mBTIncrement.setOnTouchListener( new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            if( (event.getAction()==MotionEvent.ACTION_UP || event.getAction()==MotionEvent.ACTION_CANCEL) 
                    && mAutoIncrement ){
                mAutoIncrement = false;
            }
            return false;
        }
    });  

在上面的情况中,按钮是增量按钮。创建另一个按钮,它将把mAutoDecrement设置为true。
而decrement()将是一个函数,它将像这样设置您的实例int变量:
public void decrement(){
    mValue--;
    _value.setText( ""+mValue );
}

你可以计算出增量。哦,REP_DELAY是一个静态的int变量,设置为50。
我看到这是Jeffrey Cole开源的NumberPicker的摘录,可以在http://www.technologichron.net/上获得。必须添加适当的作者归属。

我按照您的指示创建了increment()函数并声明了REP_DELAY,没有错误,但是当我点击增加按钮时,值会增加,即使我松开它,值仍在增加而且不停止。减少按钮也是如此。我错过了什么吗? - src
2
抱歉,忘记添加onTouchListener了,它将清除增量标志(减量同理)- 请参见更新的代码。 - Yar
1
已添加了onTouchListener以进行增量和减量操作,现在它可以正常工作了。谢谢!非常感谢! :) - src
感谢您的帮助。当我需要对多个值进行递增/递减操作时,有什么好的方法吗?这会导致很多重复代码。 - MikkoP
你不需要另一个线程,只需使用“handler post runnables”即可...在“touch up”时从“handler”中删除“runnable callbacks”。 - user924
虽然这个答案完全正确,但还可以进行一些优化。请查看我的答案以获取一个简化版本。 - jmart

11

虽然被接受的答案完全正确,但可以简化一些。

基本上,我们可以优化两个方面:

  • 我们不需要OnTouchListener。
  • 我们只需实例化一次可运行对象,而不是创建多个对象。

因此,这是我的版本:

// global variables
Handler handler = new Handler();
Runnable runnable;

increaseView.setOnLongClickListener(new View.OnLongClickListener() {

    @Override
    public boolean onLongClick(View v) {

        runnable = new Runnable() {
            @Override
            public void run() {
                if (!increaseView.isPressed()) return;
                increaseValue();
                handler.postDelayed(runnable, DELAY);
            }
        };

        handler.postDelayed(runnable, DELAY);
        return true;

    }

});

这里的可运行对象被重复使用。当视图不再被按下时,它将停止调用自身。

减少视图或按钮可以以类似的方式定义。


9
我虽然来晚了,但这可能会帮助任何需要更好答案的人。
我创建了一个`CounterHandler`类,使用它来实现上述连续计数功能非常容易。
你可以在下面的代码片段中找到该类及其使用方法的示例: https://gist.github.com/nomanr/d142f4ccaf55ceba22e7f7122b55b9b6
    new CounterHandler.Builder()
            .incrementalView(buttonPlus)
            .decrementalView(buttonMinus)
            .minRange(-50) // cant go any less than -50
            .maxRange(50) // cant go any further than 50
            .isCycle(true) // 49,50,-50,-49 and so on
            .counterDelay(200) // speed of counter
            .counterStep(2)  // steps e.g. 0,2,4,6...
            .listener(this) // to listen counter results and show them in app
            .build();

这是所有的内容。 :)

这看起来像是最完美的答案。 - Raghav Satyadev

5

我在长按按钮时递增值的方法是使用计时器定期检查按钮是否仍被按下,然后增加值,否则取消计时器。要更新用户界面,请使用Handler。

vh.bttAdd.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {

            final Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                 if(vh.bttAdd.isPressed()) {
                     final int track = ((ChannelAudioTrack) channels.get(vh.getAdapterPosition())).goToNextTrack();
                  updateUI(vh,track);
                 }
                else
                timer.cancel();
            }
            },100,200);

            return true;
        }
    });

处理程序:

private void updateUI(final TrackViewHolder vh, final int track)
 {
new Handler(Looper.getMainLooper()).post(new Runnable() {
                        @Override
                        public void run() {
                                  vh.tvTrackNumber.setText(Integer.toString(track));
                        }
                    }) ;
}

2

我想分享一下我自己的解决方案,这个方案对我非常有效。

首先,在您的活动中创建一个处理程序。

private Handler mHandler = new Handler();

然后,创建可运行的程序来增加/减少并显示您的数字。在这里,我们将检查您的按钮是否仍处于按下状态,如果是,则增加并重新运行可运行程序。

private Runnable incrementRunnable = new Runnable() {
    @Override
    public void run() {
        mHandler.removeCallbacks(incrementRunnable); // remove our old runnable, though I'm not really sure if this is necessary
        if(IncrementButton.isPressed()) { // check if the button is still in its pressed state
            // increment the counter
            // display the updated value here, if necessary
            mHandler.postDelayed(incrementRunnable, 100); // call for a delayed re-check of the button's state through our handler. The delay of 100ms can be changed as needed.
        }
    }
}

最后,在我们按钮的onLongClickListener中使用它。

IncrementButton.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View view) {
        mHandler.postDelayed(incrementRunnable, 0); // initial call for our handler.
        return true;
    }
});

就是这样了!


另一种方法是在OnLongClickListener内部声明处理程序和可运行程序,尽管我自己不确定这是否是一个好的做法。

IncrementButton.setOnLongClickListener(new View.OnLongClickListener() {
    private Handler mHandler = Handler();
    private Runnable incrementRunnable = new Runnable() {
        @Override
        public void run() {
            mHandler.removeCallbacks(incrementRunnable);
            if(IncrementButton.isPressed()) {
                // increment the counter
                // display the updated value here, if necessary
                mHandler.postDelayed(incrementRunnable, 100);
            }
        }
    };

    @Override
    public boolean onLongClick(View view) {
        mHandler.postDelayed(incrementRunnable, 0);
        return true;
    }
});

在进行连续递增时,建议在一定时间/递增次数后增加递增值。 例如:如果递增次数小于10,则递增1。否则,递增3。


1

看起来这个问题没有完美的解决方案,总会涉及一些复杂性。

这是我的尝试,它包含了Wiktor的答案,但提供了一个可以剪切/粘贴的完整MainActivity。

在我的示例中,复杂的部分是onLongClickListener,以及它有多深和有多少级匿名类。

然而,另一方面,简单之处在于所有内容都包含在一个相对较短的类(MainActivity)中,只有一个主要的代码块——onLongClickListener——它只定义一次,并且非常清楚“动作”代码在哪里:

package com.example.boober.aalongclickoptimizationunit;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {

    TextView valueDisplay;
    Button minusButton;
    Button plusButton;
    Button[] arrayOfControlButtons;

    Integer currentDisplayValue = 500;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        valueDisplay = findViewById(R.id.value);
        minusButton = findViewById(R.id.minusButton);
        plusButton = findViewById(R.id.plusButton);

        arrayOfControlButtons = new Button[]{plusButton, minusButton}; // this could be a large set of buttons

        updateDisplay(); // initial setting of display

        for (Button b : arrayOfControlButtons) {

            b.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(final View v) {

                    final Timer timer = new Timer();
                    timer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            if (v.isPressed()) { // important: checking if button still pressed
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        // --------------------------------------------------
                                        // this is code that runs each time the
                                        // long-click timer "goes off."
                                        switch (v.getId()) {

                                            // which button was pressed?
                                            case R.id.plusButton: {
                                                currentDisplayValue = currentDisplayValue + 10;
                                                break;
                                            }

                                            case R.id.minusButton: {
                                                currentDisplayValue = currentDisplayValue - 10;
                                                break;
                                            }
                                        }
                                        updateDisplay();
                                        // --------------------------------------------------
                                    }
                                });
                            } else
                                timer.cancel();
                        }
                    }, 100, 200);
                    // if set to false, then long clicks will propagate into single-clicks
                    // also, and we don't want that.
                    return true;
                }
            });

        }


    }

    // ON-CLICKS (referred to from XML)

    public void minusButtonPressed(View ignored) {
        currentDisplayValue--;
        updateDisplay();
    }

    public void plusButtonPressed(View ignored) {
        currentDisplayValue++;
        updateDisplay();
    }

    // INTERNAL

    private void updateDisplay() {
        valueDisplay.setText(currentDisplayValue.toString());
    }


}

1

针对Kotlin和协程用户的版本(如果您愿意,可以将GlobalScope替换为任何其他范围):

    var job: Job? = null

    viewClickable.setOnClickListener {
        // single click
    }

    viewClickable.setOnLongClickListener {
        if (job == null || !job!!.isActive) {
            job = GlobalScope.launch(Dispatchers.Main.immediate) {
                while (it.isPressed) {
                    // long press
                    delay(100)
                }
            }
        }
        true
    }

1

对于Kotlin用户

    myButton.setOnLongClickListener {
        val handler = Handler(Looper.myLooper()!!)
        val runnable : Runnable = object : Runnable {
            val number = 0
            override fun run() {
                handler.removeCallbacks(this)
                if (myButton.isPressed) {
                    val newNumber= number + 1
                    textView.text = "$newNumber Items"
                    handler.postDelayed(this, 100)
                }
            }
        }
        handler.postDelayed(runnable,0)
        true
    }

0
为了将这个任务分解成基本要求,我们需要以下内容:
  1. 需要执行的函数
  2. 重新执行的条件(作为每次检查条件的可调用函数)
  3. 重新执行之前的延迟时间
下面是一个可以复制到Utils类中的函数,可以用于满足这些要求的任何事物:
/**
 * Execute given function, and if condition is met, re-execute recursively after delay
 * @param function: function to be executed
 * @param conditionToRepeat: condition to re-execute function
 * @param delayMillis: delay after which function should be re-executed (if condition was met)
 */
fun executeRecursively(function: () -> Unit, conditionToRepeat: () -> Boolean, delayMillis: Long) {
    function()
    if (conditionToRepeat())
        Handler(Looper.getMainLooper()).postDelayed(
               { executeRecursively(function, conditionToRepeat, delayMillis) }, 
               delayMillis)

}

请求用例的使用示例:

binding.button.setOnLongClickListener {
                executeRecursively( { increase() }, // function
                                    { binding.button.isPressed }, // condition to re-execute
                                    DELAY // delay in millis before re-execution
                                  )
                true
            }

这就是你所需要的全部。但在许多情况下,您可能希望在长按时减少延迟,以便数字增加/减少得更快。以下是为此情况提供的扩展功能:

/**
 * Execute given function, and if condition is met, re-execute recursively after delay
 * @param function: function to be executed
 * @param conditionToRepeat: condition to re-execute function
 * @param delayMillis: delay after which function should be re-executed (if condition was met)
 * @param minDelayMillis: minimal delay in milliseconds
 * @param decreasingDelayMillis: amount to decrease delay for next re-execution (if minimal delay has not been reached)
 */
fun executeRecursivelyWithDecreasingDelay(function: () -> Unit, conditionToRepeat: () -> Boolean, delayMillis: Long, minDelayMillis: Long, decreasingDelayMillis: Long) {
    function()
    if (conditionToRepeat()) {
        val delay = if (delayMillis <= minDelayMillis) minDelayMillis else delayMillis - decreasingDelayMillis
        Handler(Looper.getMainLooper()).postDelayed(
            { executeRecursivelyWithDecreasingDelay(function, conditionToRepeat, delay, minDelayMillis, decreasingDelayMillis) },
            delayMillis
        )
    }
}

在这个函数中,只需添加最小的延迟(例如2毫秒),并且每次减少延迟的速率(例如2毫秒)。decreasingDelayMillis就像延迟的负速度。

0

初始化并调用方法

int speed = 0;
 button = findViewById(R.id.button);
    button.setOnTouchListener((v, event) -> {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            v.setPressed(true);
                    increment();
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            v.setPressed(false);
            speed = 0;
         }
        return true;
    });

这是增加的方法

    public void increment() {
        new Handler().postDelayed(() -> {
            Toast.makeText(FrequencyActivity.this, String.valueOf(speed), Toast.LENGTH_SHORT).show();
            if (button.isPressed()) {
                speed += 1;
                increment();
            }
        }, 200); //200 ms for fast incrementing
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接