Androidカスタムビューの乱数検証コード(模倣されたHongyang)

序文

この記事はカスタマイズのview初心者向けですが、ある程度の理論的知識や基本的な概念を持っていることを願っています。場所によっては、一筆書きで詳しく説明されない場合があります。詳細な説明の長さが長すぎます。 。

この記事は、紅陽のカスタムビュー(1)を模倣したものです。7年近く経ちましたが、それでも学習価値があると思います。

効果

ここに画像の説明を挿入

カスタムビュー分類

カスタムView分類を簡単に紹介します

  • 複合コントロールは、layoutLinearLayoutなどの既存のコントロールを継承し、LayoutInflaterレイアウトを導入して、関連するイベントを処理します。この方法の利点は、ビュー内の描画メカニズムにあまり注意を払う必要がないことです。スケーラビリティも非常に強力です。
  • 既存のシステムコントロールから継承し、次のようないくつかのプロパティまたはメソッドを変更または拡張します。AppCompatTextView
  • viewまたはから継承されたviewgroupこの種の親戚は、描画プロセスを自分で制御する必要があるため、より複雑ですが、比較的、想像力の余地もあります。

ステップ

まず、上の図の効果を分析します。

  • 色付きの長方形の背景
  • 中央揃えのテキスト

それは比較的単純で、ベテランは少し考えた後にアイデアを持っています:

  • 1.カスタム属性
  • 2.工法を追加する
  • 3.構造でカスタムスタイルを取得します
  • 4.onDraw計算された座標図を書き直します
  • 5.onMeasure測定の幅と高さを書き換えます
  • 6.クリックイベントを設定します

最初にレンダリングを分析し、次に構想し、次に継続的に調整および最適化します。

1.カスタム属性

このステップは必ずしも前に書く必要はありません。どの属性が使用されるかを事前に知ることは必ずしも可能ではないと考える人もいるかもしれません。例は比較的単純なので、とりあえず最初に置きましょう。個人的な習慣について。

  • res/values/ケースの確立attrs.xmlファイルを、私たちの内部の定義属性と私たちの文全体を样式
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="randomText" format="string"/>
    <attr name="randomTextColor" format="color"/>
    <attr name="randomTextSize" format="dimension"/>

    <declare-styleable name="RandomTextView" >
        <attr name="randomText"/>
        <attr name="randomTextColor"/>
        <attr name="randomTextSize"/>
    </declare-styleable>

</resources>

formatこれは、属性の値型です。
合計:文字列、色、次元、整数、列挙型、参照、浮動小数点数、ブール値、分数、フラグ

  • xmlレイアウト内の参照:
    <com.yechaoa.customviews.randomtext.RandomTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="20dp"
        app:randomText="1234"
        app:randomTextColor="@color/colorAccent"
        app:randomTextSize="50sp" />

はじめに注意してください命名空间

xmlns:app="http://schemas.android.com/apk/res-auto"

2.工法を追加する

新しいRandomTextViewクラスを作成し、継承しView3コンストラクターを追加します

class RandomTextView : View {
    
    

    //文本
    private var mRandomText: String

    //文本颜色
    private var mRandomTextColor: Int = 0

    //文本字体大小
    private var mRandomTextSize: Int = 0

    private var paint = Paint()
    private var bounds = Rect()

    //调用两个参数的构造
    constructor(context: Context) : this(context, null)

    //xml默认调用两个参数的构造,再调用三个参数的构造,在三个参数构造里获取自定义属性
    constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)

    constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) {
    
    
        ...
    }
    ...
}

ここで、すべての構築方法は3番目の構築方法を指し、最初の2つの構築の継承はthisではなくであることに注意してくださいsuper

たとえば、最初の構造はnewで作成でき、2番目の構造はデフォルトでxmlで呼び出され、3番目の構造でカスタム属性を取得します。

3.構造でカスタムスタイルを取得します

    constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) {
    
    
        //获取自定义属性
        val typedArray = context.theme.obtainStyledAttributes(
            attributeSet,
            R.styleable.RandomTextView,
            defStyle,
            0
        )

        mRandomText = typedArray.getString(R.styleable.RandomTextView_randomText).toString()
        mRandomTextColor = typedArray.getColor(R.styleable.RandomTextView_randomTextColor, Color.BLACK)//默认黑色
        mRandomTextSize = typedArray.getDimensionPixelSize(
            R.styleable.RandomTextView_randomTextSize,
            TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16F, resources.displayMetrics ).toInt()
        )

        //获取完回收
        typedArray.recycle()

        paint.textSize = mRandomTextSize.toFloat()

        //返回文本边界,即包含文本的最小矩形,没有所谓“留白”,返回比measureText()更精确的text宽高,数据保存在bounds里
        paint.getTextBounds(mRandomText, 0, mRandomText.length, bounds)
    }

obtainStyledAttributesカスタム属性を取得し、1が返されますTypedArrayattrs.xmlスタイル(R.styleable.RandomTextView)を宣言しているファイルここで使用される、と返さTypedArrayは内部の属性が含まれています。

カスタムビューで属性のセットを取得し、割り当て後の割り当てを使用paintして作成できます。

次に、ペイントgetTextBoundsメソッドが使用されます。

paint.getTextBounds(mRandomText, 0, mRandomText.length, bounds)

簡単に理解できるのは、テキストを長方形に入れることです。テキストの幅と高さは長方形の幅と高さでわかるため、幅と高さが格納されboundsます。境界は長方形Rectです。理由これを実行しますか?センタリング時に使用されます。

では、レイアウトの描画を開始します。

4. onDrawを書き直して、座標描画を計算します

    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas?) {
    
    
        super.onDraw(canvas)

        /**
         * 自定义View时,需要我们自己在onDraw中处理padding,否则是不生效的
         * 自定义ViewGroup时,子view的padding放在onMeasure中处理
         */

        /**
         * 矩形背景
         */
        paint.color = Color.YELLOW
        //计算坐标,因为原点是在文字的左下角,左边要是延伸出去就还要往左边去,所以是减,右边和下边是正,所以是加
        canvas?.drawRect(
            (0 - paddingLeft).toFloat(),
            (0 - paddingTop).toFloat(),
            (measuredWidth + paddingRight).toFloat(),
            (measuredHeight + paddingBottom).toFloat(),
            paint
        )

        /**
         * 文本
         */
        paint.color = mRandomTextColor
        //注意这里的坐标xy不是左上角,而是左下角,所以高度是相加的,在自定义view中,坐标轴右下为正
        //getWidth 等于 measuredWidth
        canvas?.drawText(
            mRandomText,
            (width / 2 - bounds.width() / 2).toFloat(),
            (height / 2 + bounds.height() / 2).toFloat(),
            paint
        )
    }

上記のコードはonDraw、黄色の色で長方形の背景表示し、カスタム属性の色で中央揃えのテキストを描画することです。

ここで、位置を計算するときに注意する必要があり坐标ます。カスタムビューでは、原点はビュー左上角ですが、数学座標系では、原点(0,0)中间があります。2つの間に違いがあります。

次に、xmlレイアウトpaddingにパディングがある場合、またはパディングが予期して使用されるonDraw場合は、書き換え時にパディングデータを追加する必要があります。そうしないと、パディングが有効になりません。継承されているViewGroup場合子view、パディングはonMeasure処理に配置されます。

この時点での効果を見てください。

ここに画像の説明を挿入

現時点で疑問がありますか、xmlの幅と高さは明らかwrap_contentですが、なぜそれは親レイアウトでいっぱいですか?

これは関係onMeasureする知識のポイントです、見下ろしてください。

5. onMeasureを書き直して、幅と高さを測定します

xmlでview幅と高さを設定する方法は3つあります。

  • match_parent
  • wrap_content
  • 100dpなどの特定のデータ

onMeasureでは3つのモードがあります。MeasureSpecmode

  • 正確:通常、明確な値またはMATCH_PARENTを設定します
  • AT_MOST:サブレイアウトが最大値(通常はWARP_CONTENT)に制限されていることを示します
  • UNSPECIFIED:サブレイアウトが必要な大きさであり、ほとんど使用されないことを示します

wrap_content対応するxmlを使用するAT_MOSTため、効果は親レイアウト可用空间全体に表示され、親レイアウトは画面全体に表示されるため、カスタムビューも画面全体に表示されます。

実際に必要な効果は、ビューが全画面をカバーするのではなく、それ自体をラップすることです。そのため、ビューonMeasureを処理する必要があります。

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    
    
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        /**
         * EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
         * AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
         * UNSPECIFIED:表示子布局想要多大就多大,很少使用
         */
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)

        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        var width = 0
        var height = 0

        //如果指定了宽度,或不限制宽度,用可用宽度即可,如果是WARP_CONTENT,则用文本宽度,再加上左右padding
        when (widthMode) {
    
    
            MeasureSpec.UNSPECIFIED,
            MeasureSpec.EXACTLY -> {
    
    
                width = widthSize + paddingLeft + paddingRight
            }
            MeasureSpec.AT_MOST -> {
    
    
                width = bounds.width() + paddingLeft + paddingRight
            }
        }

        //如果指定了高度,或不限制高度,用可用高度即可,如果是WARP_CONTENT,则用文本高度,再加上上下padding
        when (heightMode) {
    
    
            MeasureSpec.UNSPECIFIED,
            MeasureSpec.EXACTLY -> {
    
    
                height = heightSize + paddingTop + paddingBottom
            }
            MeasureSpec.AT_MOST -> {
    
    
                height = bounds.height() + paddingTop + paddingBottom
            }
        }

        //保存测量的宽高
        setMeasuredDimension(width, height)
    }

上記のコードは主に2つのことを行います。

  • ビューの幅と高さのモードを取得します
  • さまざまなモードの幅と高さを再測定します

最後に、setMeasuredDimension新しく測定された幅と高さ保存することを忘れないでください。そうしないと、役に立ちません

現時点では、効果はレンダリングでどのように見えるかです。

6.クリックイベントを設定します

わかりました。この時点でビューは描画されていますが、まだイベントはありません。構造に1つ追加します。点击事件

    constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) {
    
    
        ...
        /**
         * 添加点击事件
         */
        this.setOnClickListener {
    
    
            mRandomText = randomText()
            //更新
            postInvalidate()
        }
    }

randomText方法:

    /**
     * 根据文本长度 随意数字
     */
    private fun randomText(): String {
    
    
        val list = mutableListOf<Int>()
        for (index in mRandomText.indices) {
    
    
            list.add(Random.nextInt(10))
        }
        val stringBuffer = StringBuffer()
        for (i in list) {
    
    
            stringBuffer.append("" + i)
        }
        return stringBuffer.toString()
    }

イベントがトリガーされた後、テキストが更新され、ビューが更新ページを再描画します。

データ収集、つまり変更された番号に関しては、onTextChangedインターフェースまたはオープン方法アクセスを書くことができます。

総括する

実際、効果を見ると、それほどTextView単純ではなく、TextViewもレンダリングで効果を簡単に実現できます。

したがって、この記事の焦点は、効果を実現することではなく学习理解、ビューとそのカスタマイズすること绘制流程です。

理論的にどれだけ見ても、練習する必要があります。2回フォローして理解し、消化することをお勧めします。

注释それはまだ非常に詳細で、少し長い間ですらあります。

それが少しあなたを助けるならば、それを好きにしてください^ _ ^

Github

https://github.com/yechaoa/CustomViews

参照

おすすめ

転載: blog.csdn.net/yechaoa/article/details/112885341