android Spinner:如何知道项目选择是以编程方式更改的,还是通过UI由用户操作更改的

kjthegm6  于 11个月前  发布在  Android
关注(0)|答案(7)|浏览(101)

我有运行spinner的OnItemSelectedListener事件的代码。所以当我在方法中时:

public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
    // I want to do something here if it's a user who changed the the selected item
}

字符串
.我如何知道项目选择是通过编程完成的还是通过UI由用户操作完成的?

hk8txs48

hk8txs481#

我不知道这是否可以从方法内部区分出来。事实上,这是很多人面临的一个问题,当微调器被启动时,onItemSelected被触发。目前看来,唯一的解决办法是使用外部变量。

private Boolean isUserAction = false;

...

public void onItemSelected( ... ) {

    if( isUserAction ) {
       // code for user initiated selection
    } else {
       // code for programmatic selection
       // also triggers on init (hence the default false)
    }

    // reset variable, so that it will always be true unless tampered with
    isUserAction = true;
}

public void myButtonClick( ... ) {
    isUserAction = false;
    mySpinner.setSelectedItem ( ... );
}

字符串

nmpmafwu

nmpmafwu2#

使用SpinnersetOnTouchListener()方法可以相当简单地实现所需的结果:

// Instance variables
boolean spinnerTouched = false;
Spinner spinner;

// onCreate() / onCreateView() / etc. method..
spinner = ...;
spinner.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            spinnerTouched = true; // User DID touched the spinner!
        }

        return false;
    }
});
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {

        if (spinnerTouched) {
            // Do something
        }
        else {
            // Do something else
        }

    }

    @Override
    public void onNothingSelected(AdapterView<?> parentView) {
    }
});

// Your method that you use the change the spinner selection programmatically...
private void changeSpinnerSelectionProgrammatically(int pos) {
    stateSpinnerTouched = false; // User DIDN'T touch the spinner
    boolean useAnimation = false;
    spinner.setSelection(pos, useAnimation); // Calls onItemSelected()
}

字符串

1dkrff03

1dkrff033#

我做了一个新的Spinner类封装了上面提到的原则。
gist中也是如此

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;

/**
 * Used this to differentiate between user selected and prorammatically selected
 * Call {@link Spinner#programmaticallySetPosition} to use this feature.
 * Created by vedant on 6/1/15.
 */
public class Spinner extends android.widget.Spinner implements AdapterView.OnItemSelectedListener {

    OnItemSelectedListener mListener;

    /**
     * used to ascertain whether the user selected an item on spinner (and not programmatically)
     */
    private boolean mUserActionOnSpinner = true;

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

        if (mListener != null) {

            mListener.onItemSelected(parent, view, position, id, mUserActionOnSpinner);
        }
        // reset variable, so that it will always be true unless tampered with
        mUserActionOnSpinner = true;
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        if (mListener != null)
            mListener.onNothingSelected(parent);
    }

    public interface OnItemSelectedListener {
        /**
         * <p>Callback method to be invoked when an item in this view has been
         * selected. This callback is invoked only when the newly selected
         * position is different from the previously selected position or if
         * there was no selected item.</p>
         *
         * Impelmenters can call getItemAtPosition(position) if they need to access the
         * data associated with the selected item.
         *
         * @param parent The AdapterView where the selection happened
         * @param view The view within the AdapterView that was clicked
         * @param position The position of the view in the adapter
         * @param id The row id of the item that is selected
         */
        void onItemSelected(AdapterView<?> parent, View view, int position, long id, boolean userSelected);

        /**
         * Callback method to be invoked when the selection disappears from this
         * view. The selection can disappear for instance when touch is activated
         * or when the adapter becomes empty.
         *
         * @param parent The AdapterView that now contains no selected item.
         */
        void onNothingSelected(AdapterView<?> parent);
    }

    public void programmaticallySetPosition(int pos, boolean animate) {
        mUserActionOnSpinner = false;
        setSelection(pos, animate);
    }

    public void setOnItemSelectedListener (OnItemSelectedListener listener) {
        mListener = listener;
    }

    public Spinner(Context context) {
        super(context);
        super.setOnItemSelectedListener(this);
    }

    public Spinner(Context context, int mode) {
        super(context, mode);
        super.setOnItemSelectedListener(this);
    }

    public Spinner(Context context, AttributeSet attrs) {
        super(context, attrs);
        super.setOnItemSelectedListener(this);
    }

    public Spinner(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        super.setOnItemSelectedListener(this);
    }

    public Spinner(Context context, AttributeSet attrs, int defStyle, int mode) {
        super(context, attrs, defStyle, mode);
        super.setOnItemSelectedListener(this);
    }
}

字符串

uelo1irk

uelo1irk4#

我已经想出了一个简单而通用的解决方法。请参考这个问题的公认答案:
不需要的onItemSelected调用
因此,如果position不等于spin.getTag(R.id.pos),那么您知道回调是由于用户进行了更改,因为无论何时您自己进行更改,您都将标记设置为spin.setTag(R.id.pos, pos),其中pos是您设置的值。如果您使用这种方法,请确保在完成工作后在onItemSelected中设置标记!

nnt7mjpx

nnt7mjpx5#

SeekBar不同,Spinner没有内置的支持来检测更改是程序性的还是用户的,所以我建议,我从来不使用spinner来完成任何递归编程任务。当我试图实现一个MediaPlayer与一个SeekBar和一个Spinner的递归连接时,我有过非常糟糕的经历。结果令人失望。所以,只有当你喜欢不快乐和失望时,你才能尝试。

**注意:**我通过添加apply Button到我的微调器选择来解决我的问题。做一个工作不是一个好的做法,而是重新实现Spinner以具有我们自己的预期行为。

bybem2ql

bybem2ql6#

我做了一个@ban-geoengineering和@vedant的mashup。
我不喜欢isUserAction的答案。
@ban-geoengineering的答案对一个Spinner来说很可爱,但当你有多个Spinner时就不可爱了。
我不喜欢在@vedant的答案中需要一个特殊的changeSpinnerSelectionProgrammatically方法。
我认为这是一个合理的两全其美:

package com.prometheanworld.audiotest

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import android.widget.AdapterView
import androidx.appcompat.widget.AppCompatSpinner

/**
 * A subclass of AppCompatSpinner that adds `userTouched` detection
 */
@SuppressLint("ClickableViewAccessibility")
class MySpinner : AppCompatSpinner {
  companion object {
    private const val MODE_THEME = -1
  }

  /**
   * A clone of AdapterView.OnItemSelectedListener that adds a `userTouched: Boolean` parameter to each method.
   */
  interface OnItemSelectedListener {
    /**
     *
     * Callback method to be invoked when an item in this view has been
     * selected. This callback is invoked only when the newly selected
     * position is different from the previously selected position or if
     * there was no selected item.
     *
     * Implementers can call getItemAtPosition(position) if they need to access the
     * data associated with the selected item.
     *
     * @param parent The AdapterView where the selection happened
     * @param view The view within the AdapterView that was clicked
     * @param position The position of the view in the adapter
     * @param id The row id of the item that is selected
     * @param userTouched true if the user touched the view, otherwise false
     */
    fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long, userTouched: Boolean)

    /**
     * Callback method to be invoked when the selection disappears from this
     * view. The selection can disappear for instance when touch is activated
     * or when the adapter becomes empty.
     *
     * @param parent The AdapterView that now contains no selected item.
     * @param userTouched true if the user touched the view, otherwise false
     */
    fun onNothingSelected(parent: AdapterView<*>?, userTouched: Boolean)
  }

  private var userTouched = false
  private var externalOnTouchListener: OnTouchListener? = null
  private var internalOnTouchListener = OnTouchListener { v, event ->
    when (event.action) {
      MotionEvent.ACTION_DOWN -> userTouched = true
    }
    externalOnTouchListener?.onTouch(v, event) ?: false
  }
  private var externalOnItemSelectedListener: OnItemSelectedListener? = null
  private val internalOnItemSelectedListener = object : AdapterView.OnItemSelectedListener {
    override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
      externalOnItemSelectedListener?.onItemSelected(parent, view, position, id, userTouched)
      userTouched = false
    }

    override fun onNothingSelected(parent: AdapterView<*>?) {
      externalOnItemSelectedListener?.onNothingSelected(parent, userTouched)
      userTouched = false
    }
  }

  constructor(context: Context) : this(context, null)

  @Suppress("unused")
  constructor(context: Context, mode: Int) : this(context, null, R.attr.spinnerStyle, mode)

  constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, R.attr.spinnerStyle)

  constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : this(context, attrs, defStyle, MODE_THEME)

  constructor(context: Context, attrs: AttributeSet?, defStyle: Int, mode: Int) : this(context, attrs, defStyle, mode, null)

  constructor(context: Context, attrs: AttributeSet?, defStyle: Int, mode: Int, popupTheme: Resources.Theme?) :
      super(context, attrs, defStyle, mode, popupTheme) {
    super.setOnTouchListener(internalOnTouchListener)
    super.setOnItemSelectedListener(internalOnItemSelectedListener)
  }

  override fun setOnTouchListener(listener: OnTouchListener?) {
    externalOnTouchListener = listener
  }

  override fun setOnItemSelectedListener(listener: AdapterView.OnItemSelectedListener?) {
    throw UnsupportedOperationException("Use setOnItemSelectedListener(listener: MySpinner.OnItemSelectedListener?) instead")
  }

  fun setOnItemSelectedListener(listener: OnItemSelectedListener?) {
    externalOnItemSelectedListener = listener
  }
}

字符串

kg7wmglp

kg7wmglp7#

我使用了一个基于触摸模式下可聚焦的解决方案。
1.在触摸模式下将微调器视图设置为可聚焦。
1.设置微调器的焦点改变监听器,以便在焦点改变时调用微调器.performClick()。
1.在微调器的onItemSelected侦听器中,将焦点返回到布局的父视图(或您认为合适的视图)
1.用户输入可以通过检查微调器是否具有焦点来识别,因为编程更改不会请求焦点。
附言:当你在onCreate中为微调器设置focusableintouchmode时,确保你立即将焦点返回到父视图,以防你缺少任何其他可聚焦的视图。

相关问题