【问题标题】:Call requires API level 29 (current min is 21): `android.widget.NumberPicker#setTextColor`调用需要 API 级别 29(当前最低为 21):`android.widget.NumberPicker#setTextColor`
【发布时间】:2021-01-13 03:20:42
【问题描述】:

我想使用 setTextColor 更改选定的文本颜色字段。但是 Android Studio 给了我这个错误。我该怎么办?最小 SDK 为 21。 这是我的 CustomNumberPicker 类的代码:

import android.annotation.TargetApi
import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.NumberPicker
import android.widget.NumberPicker.OnScrollListener
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.widget.TextViewCompat
import ir.partsoftware.cup.R
import timber.log.Timber

class CustomNumberPicker : NumberPicker {

    constructor(context: Context?) : super(context) {
        init()
    }

    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
        init()
    }

    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        init()
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    constructor(
        context: Context?,
        attrs: AttributeSet?,
        defStyleAttr: Int,
        defStyleRes: Int
    ) : super(context, attrs, defStyleAttr, defStyleRes) {
        init()
    }

    private fun init() {
        setDividerColor(ContextCompat.getColor(context, R.color.color_secondary))
        setNumberPickerTextColor(this, ContextCompat.getColor(context, R.color.color_secondary))
        this.setOnValueChangedListener { picker, oldVal, newVal ->
            setNumberPickerTextColor(this, ContextCompat.getColor(context, R.color.color_secondary))
        }
        this.setOnScrollListener { numberPicker, scrollState ->
            setNumberPickerTextColor(this, ContextCompat.getColor(context, R.color.color_secondary))
        }
    }

    private fun setNumberPickerTextColor(numberPicker: NumberPicker, color: Int) {
        if (VERSION.SDK_INT >= VERSION_CODES.Q) {
            numberPicker.textColor = color
        } else {
            val count = numberPicker.childCount
            for (i in 0 until count) {
                val child = numberPicker.getChildAt(i)
                if (child is EditText) {
                    try {
                        child.setTextColor(color)
                        val fieldSelectorWheelPaint = numberPicker.javaClass.getDeclaredField("mSelectorWheelPaint")
                        val paint = fieldSelectorWheelPaint[numberPicker] as Paint
                        paint.color = color
                        fieldSelectorWheelPaint.isAccessible = true
                        numberPicker.invalidate()
                    } catch (ex: java.lang.Exception) {
                        // Ignore
                    }
                }
            }
        }
    }

    private fun setDividerColor(@ColorInt color: Int) {
        try {
            val fDividerDrawable =
                NumberPicker::class.java.getDeclaredField("mSelectionDivider")
            fDividerDrawable.isAccessible = true
            val d = fDividerDrawable[this] as Drawable
            DrawableCompat.setTint(d, color)
            d.invalidateSelf()
            postInvalidate()
        } catch (e: Exception) {
            Timber.d(e)
        }
    }

    override fun addView(
        child: View,
        index: Int,
        params: ViewGroup.LayoutParams
    ) {
        super.addView(child, index, params)
        updateView(child)
    }

    private fun updateView(view: View) {
        if (view is EditText) {
            try {
                TextViewCompat.setTextAppearance(view, R.style.TextAppearance_PartPay_NumPicker)
                val customFont: Typeface? = ResourcesCompat.getFont(context, R.font.iran_yekan)
                view.typeface = customFont
                //  setNumberPickerTextColor(ContextCompat.getColor(context, R.color.color_secondary))
            } catch (e: Exception) {
                Timber.d(e)
            }
        }
    }
}

【问题讨论】:

  • 在这个答案中检查一些解决方案:stackoverflow.com/questions/22962075/…
  • 在问这个问题之前我已经检查过了
  • Tapa Save 或 Eduardo Reis 提供的解决方案不适合您吗?在低于 29 的 API 中,文本颜色只能通过反射访问。因此,解决方案是 if / else 条件,其中 if >= 29 使用 setTextColor,如果低于使用反射访问字段。

标签: android numberpicker android-number-picker


【解决方案1】:

尝试下一个代码。当 API 不可访问时将使用反射:

public void setNumberPickerTextColor(final NumberPicker numberPicker, final int color){

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        numberPicker.setTextColor(color);
    }
    else {
        final int count = numberPicker.getChildCount();

        for (int i = 0; i < count; i++) {

            final View child = numberPicker.getChildAt(i);

            if (child instanceof EditText) {
                try {
                    ((EditText)child).setTextColor(color);
                    numberPicker.invalidate();

                    final Field fieldSelectorWheelPaint = numberPicker.getClass().getDeclaredField("mSelectorWheelPaint");
                    boolean     isAccessible            = fieldSelectorWheelPaint.isAccessible();
                    fieldSelectorWheelPaint.setAccessible(true);
                    final Paint paint = (Paint)fieldSelectorWheelPaint.get(numberPicker);

                    if (paint != null){
                        paint.setColor(color);
                        fieldSelectorWheelPaint.setAccessible(isAccessible);
                        numberPicker.invalidate();
                    }

                    final Field fieldSelectionDivider = numberPicker.getClass().getDeclaredField("mSelectionDivider");
                    isAccessible = fieldSelectionDivider.isAccessible();
                    fieldSelectionDivider.setAccessible(true);
                    fieldSelectionDivider.set(numberPicker, null);
                    fieldSelectionDivider.setAccessible(isAccessible);
                    numberPicker.invalidate();
                }
                catch (Exception ex) {
                    // Ignore
                }
            }
        }
    }
}

你可以在第一次获得控件的引用时调用该方法,另外如果滚动后颜色不持久,则钩住监听器,如next:

numberPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
    @Override
    public void onValueChange(final NumberPicker picker, final int oldVal, final int newVal) {
        setNumberPickerTextColor(numberPicker, Color.RED);
    }
});

或者你也可以挂钩一个滚动监听器,尽管上面的 setOnValueChangedListener 示例更优化,因为它只会在值更改时执行更新。为了改进下一个滚动方法,您可以检查滚动状态是否处于空闲状态,因此仅在滚动结束时调用它:

numberPicker.setOnScrollListener(new NumberPicker.OnScrollListener() {
    @Override
    public void onScrollStateChange(final NumberPicker numberPicker, final int scrollState) {
        setNumberPickerTextColor(numberPicker, Color.RED);
    }
});

更新下一部分仅针对您更新后的问题代码。

问题是您正在扩展 NumberPicker 类,在这种情况下,您需要在 super class 上使用 getDeclaredField。我上面的答案只能在不扩展 NumberPicker 类时使用。 此外,您将 isAccesible 放在了错误的行中,需要稍稍提前才能使其可访问。

接下来是对代码的更正,它可以在扩展 NumberPicker 类时完美使用。可以看到getDeclaredField前面是superclassisAccessible在正确的位置:

private fun setNumberPickerTextColor(numberPicker: NumberPicker, color: Int) {
    if (VERSION.SDK_INT >= VERSION_CODES.Q) {
        numberPicker.textColor = color
    } else {
        val count = numberPicker.childCount
        for (i in 0 until count) {
            val child = numberPicker.getChildAt(i)
            if (child is EditText) {
                try {
                    child.setTextColor(color)
                    val fieldSelectorWheelPaint = numberPicker.javaClass.superclass.getDeclaredField("mSelectorWheelPaint")
                    fieldSelectorWheelPaint.isAccessible = true
                    val paint = fieldSelectorWheelPaint[numberPicker] as Paint
                    paint.color = color
                    numberPicker.invalidate()
                } catch (ex: java.lang.Exception) {
                    // Ignore
                }
            }
        }
    }
}

【讨论】:

  • 你测试过这段代码吗?这段代码的问题是当我们滚动数字选择器时,颜色会回到默认值。第一次显示数字选择器,这是我想要的,但滚动后没有。
  • 挂钩一个监听器,例如 numberPicker.setOnValueChangedListener 并在 NumberPicker 的值发生变化时调用答案的方法。或者,您也可以设置滚动侦听器,例如 numberPicker.setOnScrollListener,并再次调用答案的方法,但仅当 scrollState 变为 SCROLL_STATE_IDLE 时。从这两个选项中,最佳选择是使用 setOnValueChangedListener 变体。
  • 我在我的代码中放置了 onscroll 监听器,当我在 numberpicker 上滚动时,它被正确检测到。在该侦听器中,我再次调用该函数来更改选定的文本颜色,但它不起作用,并且在滚动颜色后返回其默认值。如果您知道这个问题的答案以及我发给您链接的悬赏问题,请尽快回答,因为悬赏即将到期。
  • 您是否尝试过同时使用两个监听器? setOnValueChangedListener 和 setOnScrollListener,我已经测试过代码,它可以正常工作。
  • 颜色返回默认值或更改的唯一方法是,如果您在代码中的其他位置调用 setTextColor。确保您没有在其他任何地方调用此方法。并且始终只使用 setNumberPickerTextColor 来设置颜色。
猜你喜欢
  • 1970-01-01
  • 2017-02-23
  • 1970-01-01
  • 2015-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多