安卓自定义边栏英文索引控件

/**
* 成员信息列表 -右侧的导航条
*/
class EnglishIndexBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {

private var mIndex = -1
private var mTextSize: Int = 0
private var mSelectTextColor: Int = 0
private var mSelectTextSize: Int = 0
private var mHintTextColor: Int = 0
private var mHintTextSize: Int = 0
private var mHintCircleRadius: Int = 0
private var mWaveRadius: Int = 0
private var mContentPadding: Int = 0
private var mBarWidth: Int = 0
private var mIndexWord : String? = "A"
private var isUpdateView : Boolean = false
private var mSlideBarRect: RectF= RectF()
private lateinit var mTextPaint: TextPaint
private lateinit var mPaint: Paint
private lateinit var mWavePaint: Paint
private lateinit var mHintPaint: Paint
private var mSelect: Int = 0
private var mPreSelect: Int = 0
private var mNewSelect: Int = 0
private lateinit var mTtextBgPaint: Paint
private var mRatioAnimator: ValueAnimator? = null
private var mAnimationRatio: Float = 0.toFloat()
private var mListener: OnLetterChangeListener? = null
private var mTouchY: Int = 0
private lateinit var mBitmap: Bitmap
private var mIsActionDown: Boolean = false
private var mIsShowWave: Boolean = true
private var mViewPager: NoScrollViewPager? = null
private var mLetters = arrayOf("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",
"L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z") //设置字母索引数据

init {
initAttribute(attrs, defStyleAttr)
initData()
}

private fun initAttribute(attrs: AttributeSet?, defStyleAttr: Int) {
val typeArray = context.obtainStyledAttributes(attrs, R.styleable.EnglishIndexBarTwo, defStyleAttr, 0)
mTextSize = typeArray.getDimensionPixelOffset(R.styleable.EnglishIndexBarTwo_textSize, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 9f,
resources.displayMetrics).toInt())
mSelectTextColor = typeArray.getColor(R.styleable.EnglishIndexBarTwo_selectTextColor, Color.parseColor("#FFFFFF"))
mSelectTextSize = typeArray.getDimensionPixelOffset(R.styleable.EnglishIndexBarTwo_selectTextSize, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f,
resources.displayMetrics).toInt())
mHintTextColor = typeArray.getColor(R.styleable.EnglishIndexBarTwo_hintTextColor, Color.parseColor("#FFFFFF"))
mHintTextSize = typeArray.getDimensionPixelOffset(R.styleable.EnglishIndexBarTwo_hintTextSize,
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16f,
resources.displayMetrics).toInt())
mHintCircleRadius = typeArray.getDimensionPixelOffset(R.styleable.EnglishIndexBarTwo_hintCircleRadius,
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24f,
resources.displayMetrics).toInt())
mWaveRadius = 20
mContentPadding = 2
mBarWidth = typeArray.getDimensionPixelOffset(R.styleable.EnglishIndexBarTwo_barWidth,
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f,
resources.displayMetrics).toInt())
if (mBarWidth == 0) {
mBarWidth = 2 * mTextSize
}
typeArray.recycle()
}

@TargetApi(Build.VERSION_CODES.KITKAT)
private fun initData() {
mTextPaint = TextPaint()
mPaint = Paint()
mTextPaint.isAntiAlias = true
mPaint.isAntiAlias = true
mTtextBgPaint = Paint()
mTtextBgPaint.isAntiAlias = true
mWavePaint = Paint()
mWavePaint.isAntiAlias = true
mHintPaint = Paint()
mSelect = -1
mBitmap = BitmapFactory.decodeResource(resources, R.drawable.bg_index_water)

}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val contentLeft = (measuredWidth - mBarWidth ).toFloat()
val contentRight = (measuredWidth ).toFloat()
//val contentTop = mBarPadding.toFloat()
val contentBottom = (measuredHeight).toFloat()
mSlideBarRect.set(contentLeft, 0f, contentRight, contentBottom)

}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//绘制slide bar 上字母列表
drawLetters(canvas)
}

/**
* 绘制slide bar 上字母列表
*/
private fun drawLetters(canvas: Canvas) {
//绘制圆角矩形
mPaint.style = Paint.Style.FILL
mPaint.color = Color.parseColor("#00000000")
canvas.drawRoundRect(mSlideBarRect, mBarWidth / 2.0f, mBarWidth / 2.0f, mPaint)
//绘制描边
canvas.drawRoundRect(mSlideBarRect, mBarWidth / 2.0f, mBarWidth / 2.0f, mPaint)
//顺序绘制文字
val itemHeight = (mSlideBarRect.bottom - mSlideBarRect?.top - (mContentPadding * 2).toFloat()) / mLetters.size
for (index in mLetters.indices) {
val baseLine = getTextBaseLineByCenter(
mSlideBarRect.top + mContentPadding.toFloat() + itemHeight * index + itemHeight / 2, mTextPaint, mTextSize)
mTextPaint.textSize = mTextSize.toFloat()
mTextPaint.textAlign = Paint.Align.CENTER
val pointX = mSlideBarRect.left + (mSlideBarRect.right - mSlideBarRect.left) / 2.0f

if(mLetters.contains(mIndexWord) && isUpdateView && mIndex == index){ //标识列表已经匹配的字母
isUpdateView = false
mTtextBgPaint.color = Color.parseColor("#61BE82")
mTextPaint.color = Color.parseColor("#FFFFFF")
if(mSelect != -1 && mSelect < mLetters.size){ // 绘制提示字符
if(mIsShowWave) {
canvas.drawBitmap(mBitmap, pointX - SystemUtil.sp2px(context, 48),
baseLine - SystemUtil.sp2px(context, 15), mWavePaint)
}
if (mSelect != -1) {
mHintPaint.color = mHintTextColor
mHintPaint.textSize = SystemUtil.sp2px(context,15).toFloat()
mHintPaint.textAlign = Paint.Align.CENTER
canvas.drawText(mLetters[index], pointX-SystemUtil.sp2px(context,35), baseLine+SystemUtil.sp2px(context,3), mHintPaint)
}
}
}else{
mTtextBgPaint.color = Color.parseColor("#00000000")
mTextPaint.color = Color.parseColor("#555555")
}
canvas.drawCircle( pointX, baseLine-dp2px(3),SystemUtil.sp2px(context,7).toFloat(),mTtextBgPaint)
canvas.drawText(mLetters[index], pointX, baseLine, mTextPaint)

}
}
/**
* dp转px
* @param dpValue
* @return
*/
fun dp2px(dpValue: Int): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue.toFloat(), resources.displayMetrics).toInt()
}
/**
* 通过获取到的字母来匹配字母检索列表的字母背景
* @parem Words
*/
fun notifiChangeIndexWordBg(word : String?){
if(mLetters.contains(word)){
this.mIndexWord = word
this.mIndex = mLetters.indexOf(word)
invalidate() //刷新界面,只会调用onDraw()方法
isUpdateView = true
}
}

/**
* 给定文字的center获取文字的base line
*/
private fun getTextBaseLineByCenter(center: Float, paint: TextPaint, size: Int): Float {
paint.textSize = size.toFloat()
val fontMetrics = paint.fontMetrics
val height = fontMetrics.bottom - fontMetrics.top
return center + height / 2 - fontMetrics.bottom
}

override fun dispatchTouchEvent(event: MotionEvent): Boolean {
val y = event.y
val x = event.x
mPreSelect = mSelect
mNewSelect = (y / (mSlideBarRect.bottom - mSlideBarRect.top) * mLetters.size).toInt()
when (event.action) {
MotionEvent.ACTION_DOWN -> {
//保证down的时候在bar区域才相应事件
mViewPager?.setNoScroll(true)
if (x < mSlideBarRect.left || y < mSlideBarRect.top || y > mSlideBarRect.bottom) {
mIsActionDown = true
mIsShowWave = true
if(mNewSelect != -1 && mNewSelect < mLetters.size) {
mSelect = mNewSelect
if (mListener != null) {
mListener?.onLetterChange(mLetters[mNewSelect])
}
notifiChangeIndexWordBg(mLetters[mNewSelect])
}
val circleCenterX = measuredWidth + mHintCircleRadius - (2.0f * mWaveRadius
+ 2.0f * mHintCircleRadius) * mAnimationRatio
val canvas = Canvas()
canvas.drawBitmap(mBitmap, circleCenterX - SystemUtil.dp2px(context, 5),
mTouchY.toFloat() - SystemUtil.dp2px(context, 3), mWavePaint)
mViewPager?.postDelayed({
isUpdateView = true
mIsShowWave = false
invalidate()
},1000)
return false
}
mTouchY = y.toInt()
startAnimator(1.0f)
}
MotionEvent.ACTION_MOVE -> {
mIsActionDown = false
mIsShowWave = true
mTouchY = y.toInt()
if (mPreSelect != mNewSelect && mNewSelect >= 0 && mNewSelect < mLetters.size) {
mSelect = mNewSelect
if (mListener != null) {
mListener?.onLetterChange(mLetters[mNewSelect])
}
if(mSelect != -1) {
notifiChangeIndexWordBg(mLetters[mSelect])
}
}
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
startAnimator(0f)
mSelect = -1
mViewPager?.setNoScroll(false)
}
else -> {
}
}
return true
}

/**
* get activity‘s ViewPager
*/
fun getActivitysViewpager(viewPager: NoScrollViewPager){
mViewPager = viewPager
}
@SuppressLint("NewApi")
private fun startAnimator(value: Float) {
if (mRatioAnimator == null) {
mRatioAnimator = ValueAnimator()
}
mRatioAnimator?.cancel()
mRatioAnimator?.setFloatValues(value)
mRatioAnimator?.addUpdateListener { value ->
mAnimationRatio = value.animatedValue as Float
//球弹到位的时候,并且点击的位置变了,即点击的时候显示当前选择位置
if (mAnimationRatio == 1f && mPreSelect != mNewSelect) {
if (mNewSelect >= 0 && mNewSelect < mLetters.size) {
mSelect = mNewSelect
if (mListener != null) {
mListener?.onLetterChange(mLetters[mNewSelect])
}
if(mSelect != -1) {
notifiChangeIndexWordBg(mLetters[mSelect])
}
}
}
invalidate()
}
mRatioAnimator?.start()
}

fun setOnLetterChangeListener(listener: OnLetterChangeListener) {
this.mListener = listener
}

interface OnLetterChangeListener {
fun onLetterChange(letter: String)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
这里需要注意的就是Adapter控制器,它需要把数据和第一个字的首字母关联起来,通过创建一个数据实体类:

/**
* 悬浮窗实体
* @author guotianhui
*/
public class TitleEntity{

private AllMemberData mValue;
private String mSortLetters;

public AllMemberData getValue() {
return mValue;
}

public void setValue(AllMemberData value) {
mValue = value;
}

public String getSortLetters() {
return mSortLetters;
}

public void setSortLetters(String sortLetters) {
mSortLetters = sortLetters;
}

@Override
public String toString() {
return "TitleEntity{" +
"mValue=" + mValue +
", mSortLetters=‘" + mSortLetters + ‘\‘‘ +
‘}‘;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
当从网络获取到数据之后,我们就可以通过获取到数据之后,就可以把数据转换成我们需要的格式:

for (titleItem in result.data!!) {
val titleEntity = TitleEntity()
titleEntity.value = titleItem
titleEntity.sortLetters = getEnglishIndexFristWord(titleItem)
mMemberInfotList.add(titleEntity)
}
1
2
3
4
5
6
其中RecyclerView悬浮的指示条是通过自定义分割线的形式自定义出来的:

mRecyclerView.addItemDecoration (TitleItemDecoration(context))
1
指示条的自定义代码如下:

/**
* 所有成员列表头部的悬浮title
* @author guotianhui
*/
public class TitleItemDecoration extends RecyclerView.ItemDecoration {

private int mItemHeight;
private int mTextPadding;
private int mTextSize;
private int mTextColor;
private int mBackgroundColor;
private TextPaint mTitleTextPaint;
private Paint mBackgroundPaint;

public TitleItemDecoration(Context context) {
mItemHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 23, context.getResources().getDisplayMetrics());
mTextPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 21, context.getResources().getDisplayMetrics());
mTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, context.getResources().getDisplayMetrics());
mTextColor = Color.parseColor("#666666");
mBackgroundColor = Color.parseColor("#f4f4f4");
mTitleTextPaint = new TextPaint();
mTitleTextPaint.setAntiAlias(true);
mTitleTextPaint.setTextSize(mTextSize);
mTitleTextPaint.setColor(mTextColor);
mBackgroundPaint = new Paint();
mBackgroundPaint.setAntiAlias(true);
mBackgroundPaint.setColor(mBackgroundColor);
}

/**
* 绘制标题
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (parent.getAdapter() == null || !(parent.getAdapter() instanceof AllMemberDataAdapter)) {
return;
}
AllMemberDataAdapter adapter = (AllMemberDataAdapter) parent.getAdapter();
if (adapter.getMItemFirstWordList() == null || adapter.getMItemFirstWordList().isEmpty()) {
return;
}
for (int i = 0; i < parent.getChildCount(); i++) {
final View child = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(child);
if (titleAttachView(child, parent)) {
drawTitleItem(c, parent, child, adapter.getSortLetters(position));
}
}
}

/**
* 绘制悬浮标题
*/
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (parent.getAdapter() == null || !(parent.getAdapter() instanceof AllMemberDataAdapter)) {
return;
}
AllMemberDataAdapter adapter = (AllMemberDataAdapter) parent.getAdapter();
if (adapter.getMItemFirstWordList() == null || adapter.getMItemFirstWordList().isEmpty()) {
return;
}
View firstView = parent.getChildAt(0);
int firstAdapterPosition = parent.getChildAdapterPosition(firstView);
c.save();
//找到下一个标题对应的adapter position
int nextLetterAdapterPosition = adapter.getNextSortLetterPosition(firstAdapterPosition);
if (nextLetterAdapterPosition != -1) {
//下一个标题view index
int nextLettersViewIndex = nextLetterAdapterPosition - firstAdapterPosition;
if (nextLettersViewIndex < parent.getChildCount()) {
View nextLettersView = parent.getChildAt(nextLettersViewIndex);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) nextLettersView.getLayoutParams();
int nextToTop = nextLettersView.getTop() - params.bottomMargin - parent.getPaddingTop();
if (nextToTop < mItemHeight * 2) {
//有重叠
c.translate(0, nextToTop - mItemHeight * 2);
}
}
}
mBackgroundPaint.setColor(mBackgroundColor);
c.drawRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(),
parent.getPaddingTop() + mItemHeight, mBackgroundPaint);
mTitleTextPaint.setTextSize(mTextSize);
mTitleTextPaint.setColor(mTextColor);
c.drawText(adapter.getSortLetters(firstAdapterPosition),
parent.getPaddingLeft() + firstView.getPaddingLeft() + mTextPadding,
getTextBaseLineByCenter(parent.getPaddingTop() + mItemHeight / 2, mTitleTextPaint),
mTitleTextPaint);
c.restore();
}

/**
* 设置空出绘制标题的区域
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (titleAttachView(view, parent)) {
outRect.set(0, mItemHeight, 0, 0);
} else {
super.getItemOffsets(outRect, view, parent, state);
}
}

/**
* 绘制标题信息
*/
private void drawTitleItem(Canvas c, RecyclerView parent, View child, String letters) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
//绘制背景
c.drawRect(parent.getPaddingLeft(), child.getTop() - params.bottomMargin - mItemHeight,
parent.getWidth() - parent.getPaddingRight(), child.getTop() - params.bottomMargin, mBackgroundPaint);

float textCenterY = child.getTop() - params.bottomMargin - mItemHeight / 2;
//绘制标题文字
c.drawText(letters, parent.getPaddingLeft() + child.getPaddingLeft() + mTextPadding,
getTextBaseLineByCenter(textCenterY, mTitleTextPaint), mTitleTextPaint);
}
public float getTextBaseLineByCenter(float center, TextPaint paint) {
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float height = fontMetrics.bottom - fontMetrics.top;
return center + height / 2 - fontMetrics.bottom;
}
/**
* 判断指定view的上方是否要空出绘制标题的位置
*
* @param view  指定的view
* @param parent 父view
*/
private boolean titleAttachView(View view, RecyclerView parent) {
if (parent.getAdapter() == null || !(parent.getAdapter() instanceof AllMemberDataAdapter)) {
return false;
}
AllMemberDataAdapter adapter = (AllMemberDataAdapter) parent.getAdapter();
if (adapter.getMItemFirstWordList() == null || adapter.getMItemFirstWordList().isEmpty()) {
return false;
}
int position = parent.getChildAdapterPosition(view);
//第一个一定要空出区域 + 每个都和前面一个去做判断,不等于前一个则要空出区域
return position == 0 ||
null != adapter.getMItemFirstWordList().get(position) && !adapter.getSortLetters(position).equals(adapter.getSortLetters(position -1));

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
下面就是Adapter的代码:

/**
*所有成员列表的Adapter
* @author guotianhui
*/
class AllMemberDataAdapter : BaseQuickAdapter<TitleEntity, BaseViewHolder> {

var mItemFirstWordList: ArrayList<TitleEntity>

constructor(layoutResId: Int, dataList: ArrayList<TitleEntity>) : super(layoutResId, dataList) {
mItemFirstWordList = dataList
}

fun getSortLetters(position: Int): String? {
return mItemFirstWordList[position].sortLetters
}

fun getSortLettersFirstPosition(letters: String): Int {
if (mItemFirstWordList == null || mItemFirstWordList.isEmpty()) {
return -1
}
var position = -1
for (index in mItemFirstWordList.indices) {
if (mItemFirstWordList[index].sortLetters == letters) {
position = index
break
}
}
return position
}

fun getNextSortLetterPosition(position: Int): Int {
if (mItemFirstWordList == null || mItemFirstWordList.isEmpty() || mItemFirstWordList.size <= position + 1) {
return -1
}
var resultPosition = -1
for (index in position + 1 until mItemFirstWordList.size) {
if (mItemFirstWordList[position]!= mItemFirstWordList[index]) {
resultPosition = index
break
}
}
return resultPosition
}

override fun getItemCount(): Int {
return if (mItemFirstWordList == null) 0 else mItemFirstWordList.size
}

override fun convert(helper: BaseViewHolder?, titleItem: TitleEntity?) {
val item = titleItem?.value
val headerImageView = helper?.getView<AppCompatImageView>(R.id.iv_member_header)

if(ObjectUtils.isNotEmpty(item?.studentHeadPic)) {
ImageLoader.newInstance().loadImageHeader(headerImageView, item?.studentHeadPic, R.drawable.icon_header_default)
}else{
headerImageView?.setImageResource(R.drawable.icon_header_default)
}
if(ObjectUtils.isNotEmpty(item?.nickname)) {
helper?.setText(R.id.tv_member_name, item?.nickname)
}else{
helper?.setText(R.id.tv_member_name, item?.studentName)
}
if(item?.studentGender ==1){ //1是男 2是女,0不要展示
helper?.setImageResource(R.id.iv_member_sex, R.drawable.ic_male_member)
}else{
helper?.setImageResource(R.id.iv_member_sex, R.drawable.ic_member_female)
}
if(item?.studentVIPFlag == true){
helper?.setVisible(R.id.iv_member_coach_state,true)
}else{
helper?.setVisible(R.id.iv_member_coach_state,false)
}
if(item?.studentLevel!! >=0) { //兼容小于0的情况
helper?.setText(R.id.tv_user_level, "Lv. " + item?.studentLevel.toString())
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
以上就是这个控件功能的所有代码实现。下面的是我个人的自定义控件学习心得:安卓自定义控件,除了自定义一个自定义一个ViewGroup以外,我们还可以自定义一个View,虽然都是自定义。却又有很大的不同。因为自定义ViewGroup可以理解为自定义了一个布局容器,这个布局容器的控件其实是可以自己通过Xml布局文件来定义的。但是如果你是自定义View,也就是说你直接继承自View。因为View本身就是一个父类。而且View不可以像ViewGroup一样添加一个布局。这样我们就只能通过实现View的onMeasure()、onDraw()、onLayout()方法。这个三个方法,通过字面意思我们可以知道它们的调用顺序,先是需要测量获取自定义控件的框高,然后在调用onDarw绘制。
---------------------

原文地址:https://www.cnblogs.com/hyhy904/p/10995886.html

时间: 2024-12-18 06:45:21

安卓自定义边栏英文索引控件的相关文章

Android 自定义View之自绘控件

首先要提前声明一下,我对于自定义View的理解并不是很深,最近啃了几天guolin博主写的关于自定义View的博客,讲的非常棒,只不过涉及到源码和底层的一些东西,我自己就懵逼了,目前只是会了关于自定义View的简单使用,不过还是要写出来,当做练习了,哈哈~对于一些没有接触过的初学者,希望会有所帮助,共同成长: 按类型,自定义View可以分为三种:自绘控件.组合控件.继承控件,对于这三种类型,我会写三篇博客来分别介绍和使用. 自定义View中有三个非常重要的方法,分别为: onMeasure():

[Swift通天遁地]九、拔剑吧-(3)创建多种自定义Segment分段样式的控件

本文将演示创建多种自定义Segment分段样式的控件. 首先确保已经安装了所需的第三方类库.双击查看安装配置文件[Podfile] 1 platform :ios, '12.0' 2 use_frameworks! 3 4 target 'DemoApp' do 5 source 'https://github.com/CocoaPods/Specs.git' 6 pod 'PagingMenuController' 7 end 根据配置文件中的相关设置,安装第三方类库. 安装完成之后,双击打开

自定义日历(四)-区间选择控件

目录 一.概述 二.效果展示 三.整体结构 四.分析实现 1.QPickDate 2.QDatePanel 3.QDateWidget.QDateContent 4. 调度绘制 五.相关文章 原文链接:自定义日历(四)-区间选择控件 一.概述 很早很早以前,写过几篇关于日历的文章,不同于Qt原生的控件,这些控件都是博主使用自绘的方式进行完成,因此可定制性更强一些,感兴趣的可以参考自定义日历(一).自定义日历(二)和自定义日历(三)). 本篇文章还是继续来写我们的日历控件,仍然采用自绘的方式,带来

Android 自定义View修炼-打造完美的自定义侧滑菜单/侧滑View控件(转)

一.概述 在App中,经常会出现侧滑菜单,侧滑滑出View等效果,虽然说Android有很多第三方开源库,但是实际上 咱们可以自己也写一个自定义的侧滑View控件,其实不难,主要涉及到以下几个要点: 1.对Android中Window类中的DecorView有所了解 2.对Scroller类实现平滑移动效果 3.自定义ViewGroup的实现 首先来看看效果图吧:    下面现在就来说说这里咱们实现侧滑View的基本思路吧,这里我采用的是自定义一个继承于RelativeLayout的控件叫做XC

【Android】Android自定义带board的圆角控件

介绍 圆角控件常用于头像,按钮,图标等,用途十分广泛,而且常常配合board使用. 在IOS中,UIVIew的CALayer层已经提供了圆角和board的方法,所以圆角控件的制作非常简单,只需要类似以下简单代码即可实现: view.layer.cornerRadius = 20; view.layer.borderColor = [UIColor yellowColor].CGColor; view.layer.borderWidth = 10; view.clipsToBounds = YES

自定义的jquery ui树控件

简单的自定义jquery ui树控件,用于机构人员,支持自动加载下级节点数据 jQuery.widget("xway.Tree", { _Node: function(data) { this.id = data.type + "_" +data.id; this.trid = "tr_" + this.id; this.label = data.label; this.parent = null; this.tree = null; this.

iOS快速自定义UITabBarController内的tabbar控件

思路:1.定义一个BaseTabBarViewController类继承自UITabBarController 2.将原先的tabbar隐藏,自定义一个控件覆盖在上面 3.在控件内增加可以点击的按钮,调整控件和按钮的具体细节 具体代码如下:.h里#import <UIKit/UIKit.h>#import "FirstViewController.h"#import "SecondViewController.h"#import "ThirdV

动手分析安卓仿QQ联系人列表TreeView控件

因项目需要需要用到仿QQ联系人列表的控件样式,于是网上找到一个轮子(https://github.com/TealerProg/TreeView),工作完成现在简单分析一下这个源码.   一. 需要用到的知识如下: ①安卓事件分发机制:(http://blog.csdn.net/lvxiangan/article/details/9309927  或 http://gundumw100.iteye.com/blog/1052270) ②安卓View绘制:http://blog.csdn.net/

自定义DateTimeInput(时间)控件的显示格式

DateTimeInput控件已有的几种格式可以在Format属性中选择: 但这几种格式仍无法满足我的要求怎么办? 例如想将显示格式定为类似这样的格式:2010-06-11 20:02:52,两步搞定: (1)将Format属性设为Custom(自定义): (2)在CustomFormat中填入格式字符串. 前面的时间对应的格式为yyyy-MM-dd HH:mm:ss