序文
この記事はカスタマイズのview
初心者向けですが、ある程度の理論的知識や基本的な概念を持っていることを願っています。場所によっては、一筆書きで詳しく説明されない場合があります。詳細な説明の長さが長すぎます。 。
この記事は、紅陽のカスタムビュー(1)を模倣したものです。7年近く経ちましたが、それでも学習価値があると思います。
効果
カスタムビュー分類
カスタムView
分類を簡単に紹介します。
- 複合コントロールは、
layout
LinearLayoutなどの既存のコントロールを継承し、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
クラスを作成し、継承しView
、3
コンストラクターを追加します
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が返されますTypedArray
。attrs.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つのモードがあります。MeasureSpec
mode
- 正確:通常、明確な値または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