说明
上次我们带大家一起写了 《自定义View入门--评分控件RatingBar http://08643.cn/p/2a4d707e1889》,如果没有看的可以去看下,上节课的内容主要是交互控件,什么是交互控件呢? 就是我们用户去触摸该控件,也就是需要我们处理onTouch()事件。我们在项目开发过程中,肯定会或多或少都会遇到一些城市列表搜索、地区列表联动效果,那么这节课我们继续来写示例——自定义View入门--字母索引列表。-
效果
思路分析
但凡是这种交互控件,步骤只有两步:
1>:实现默认效果——即就是刚打开app后进来的效果
用画笔绘制 "A"——自定义View
复写onMeasure()方法:目的就是指定宽高
2>:处理交互手指上面的触摸效果
效果是:手指触摸高亮显示当前位置-
代码中分析如下:
代码如下
/**
* Email 2185134304@qq.com
* Created by JackChen on 2018/1/30.
* Version 1.0
* Description:
*/
public class LetterSideBar extends View {
// 定义26个字母
public static String[] mLetters = {"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", "#"};
private Paint mPaint;
//当前触摸的位置字母
private String mCurrentTouchLetter ;
public LetterSideBar(Context context) {
this(context, null);
}
public LetterSideBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LetterSideBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setAntiAlias(true);
//自定义属性,颜色、字体大小 这里是写死的,没有写自定义属性
mPaint.setTextSize(sp2px(16));// 设置的是像素
//默认颜色
mPaint.setColor(Color.BLUE);
}
// sp 转 px
private float sp2px(int sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
sp, getResources().getDisplayMetrics());
}
/**
* 测量控件的宽高
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//宽度 = 左右的padding + 字母宽度(取决于你的画笔) 详情见图即可
//获取字母宽度
int textWidth = (int) mPaint.measureText("A");
//宽度 [计算指定宽度]
int width = getPaddingLeft() + getPaddingRight() + textWidth ;
//高度 [可以直接获取]
int height = MeasureSpec.getSize(heightMeasureSpec) ;
//算出宽高后测量宽高
setMeasuredDimension(width , height);
}
/**
* 画26个字母
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
// int x = getPaddingLeft() ;
//每一个字母的高度 总共高度/字母总个数
int itemHeight = (getHeight() - getPaddingTop() - getPaddingBottom()) / mLetters.length ;
for (int i = 0; i < mLetters.length; i++) {
//知道每个字母中心的位置
//第一个字母中心位置是:字母高度的一半,
// 第二个字母中心位置是:字母高度一半[itemHeight/2] + 前边字母的高度[i*itemHeight]
int letterCenterY = itemHeight/2 + i*itemHeight + getPaddingTop() ;
//基线,基于中心位置 [基线求法为套路,记住即可]
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics() ;
int dy = (int) ((fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom);
int baseLine = letterCenterY + dy ;
//获取字母宽度
int textWidth = (int) mPaint.measureText(mLetters[i]);
int x = getWidth()/2 - textWidth/2 ;
//如果当前字母高亮,将画笔设置为空色 ;否则设置为蓝色
if (mLetters[i].equals(mCurrentTouchLetter)){
mPaint.setColor(Color.RED);
canvas.drawText(mLetters[i], x, baseLine, mPaint);
}else{
mPaint.setColor(Color.BLUE);
canvas.drawText(mLetters[i] , x , baseLine , mPaint);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
//ACTION_DOWN和ACTION_MOVE效果相同
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
//计算出当前的字母,获取当前手指触摸的位置,然后重新绘制
float currentMoveY = event.getY();
//当前位置 = currentMoveY/字母高度 ,
int itemHeight = (getHeight() - getPaddingTop() - getPaddingBottom()) / mLetters.length;
int currentPosition = (int) (currentMoveY/itemHeight);
if (currentPosition < 0){
currentPosition = 0 ;
}
if (currentPosition > mLetters.length- 1){
currentPosition = mLetters.length - 1 ;
}
mCurrentTouchLetter = mLetters[currentPosition] ;
if (mListener != null){
mListener.touch(mCurrentTouchLetter , true);
}
//重新绘制
invalidate();
break;
case MotionEvent.ACTION_UP:
if (mListener != null){
mListener.touch(mCurrentTouchLetter , false);
}
break;
}
return true;
}
private LetterTouchListener mListener ;
public interface LetterTouchListener{
void touch(CharSequence letter , boolean isTouch) ;
}
public void setonLetterTouchListener(LetterTouchListener listener){
this.mListener = listener ;
}
}
public class MainActivity extends AppCompatActivity implements LetterSideBar.LetterTouchListener{
private TextView letter_tv;
private LetterSideBar letter_side_bar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
letter_tv = (TextView) findViewById(R.id.letter_tv);
letter_side_bar = (LetterSideBar) findViewById(R.id.letter_side_bar);
letter_side_bar.setonLetterTouchListener(this);
}
@Override
public void touch(CharSequence letter , boolean isTouch) {
if (isTouch){ //isTouch为true表示移动手指,让中间的文字显示,并且设置文字
letter_tv.setVisibility(View.VISIBLE);
letter_tv.setText(letter);
}else{ //isTouch为false表示手指抬起,让中间的文字隐藏
letter_tv.setVisibility(View.GONE);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.view.demo7.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/letter_tv"
android:text="A"
android:textSize="22sp"
android:textColor="#FF0000"
android:layout_centerInParent="true"
/>
<com.view.demo7.LetterSideBar
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_alignParentRight="true"
android:id="@+id/letter_side_bar"
/>
</RelativeLayout>
具体代码已上传至github,里边已含相关注释:
https://github.com/shuai999/View_day07