1. 说明
前边的6次课,我们写的效果主要有自定义View的入门、自定义TextView、QQ运动运动步数、玩转字体变色、仿QQ运动步数进度条、3个形状来回切换,这些效果都是不涉及用户与自定义View控件交互的,那么我们这节课就写一些交互控件的自定义View。
这节课交互控件是第2次效果,第一次效果是仿淘宝的评分控件。
2. 效果如下
3. 思路分析
这个也属于交互控件的自定义View,我们采用的思路如下:
1>:首先先写app刚进入时看见的默认效果:
定义自定义View,然后用画笔来绘制 A - Z;
复写onMeasure()方法来指定宽高;
2>:处理用户手指在上边触摸的效果;
手指触摸当前位置,显示高亮;
中间显示当前选择的字母;
4. 代码分析如下
自定义的LetterSlideBar.java 字母选择列表如下:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/3/19 16:40
* Version 1.0
* Params:
* Description: 字母选择列表
*/
public class LetterSlideBar extends View {
// 画笔
private Paint mPaint ;
// 定义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 String mCurrentTouchLetter ;
public LetterSlideBar(Context context) {
this(context,null);
}
public LetterSlideBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public LetterSlideBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 初始化画笔
initPaint() ;
}
private void initPaint() {
mPaint = new Paint() ;
mPaint.setAntiAlias(true);
mPaint.setDither(true);
// 其实可以在attrs.xml资源文件中写一些自定义属性,这里为了方便就直接写死了
mPaint.setTextSize(sp2px(12));
// 画笔默认颜色
mPaint.setColor(Color.BLUE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 计算指定的宽度 = 左右的padding + 字母的宽度
int textWidth = (int) mPaint.measureText("A"); // 根据 "A" 测量宽度,测量其余字母也是可以的
int width = getPaddingLeft() + getPaddingRight() + textWidth ;
// 高度可以直接获取
int height = MeasureSpec.getSize(heightMeasureSpec) ;
setMeasuredDimension(width , height);
}
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
// 画26个字母 每个字母的高度
int itemHeight = (getHeight() - getPaddingTop() - getPaddingBottom())/mLetters.length ;
for (int i = 0; i < mLetters.length; i++) {
// 已知每个字母的中心位置
int letterCenterY = i * itemHeight + itemHeight/2 + getPaddingTop() ;
// x 绘制在最中间 = 宽度/2 - 文字/2
int textWidth = (int) mPaint.measureText(mLetters[i]);
int x = getWidth()/2 - textWidth/2 ;
// dy
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
int dy = (int) ((fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom);
int baseLine = letterCenterY + dy ;
// 当前触摸字母,设置为红色高亮,然后去绘制
if (mLetters[i].equals(mCurrentTouchLetter)){
mPaint.setColor(Color.RED);
canvas.drawText(mLetters[i] , i , baseLine , mPaint);
}else{
mPaint.setColor(Color.BLUE);
canvas.drawText(mLetters[i] , i , baseLine , mPaint);
}
}
}
/**
* 处理用户手指触摸字母
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
// 根据当前位置,计算出当前触摸的字母
switch (event.getAction()){
// case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// 当前手指移动的位置
float currentMoveY = event.getY() ;
// 每一个字母的高度
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] ;
// 不断的重新绘制
invalidate();
// 手指移动的时候,让其触摸为true
if (mListener != null){
mListener.touch(mCurrentTouchLetter , true);
}
break;
case MotionEvent.ACTION_UP:
// 手指抬起的时候,让其触摸为false
if (mListener != null){
mListener.touch(mCurrentTouchLetter , false);
}
break;
}
return true;
}
private LetterTouchListener mListener ;
// 字母触发的监听器 , 为了在MainActivity中提供方法,目的是传递用户触摸了哪个字母、是否触摸
public interface LetterTouchListener{
void touch(CharSequence letter , boolean isTouch) ;
}
public void setOnLetterTouchListener(LetterTouchListener listener){
this.mListener = listener ;
}
// sp 转 px
private float sp2px(int sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
sp, getResources().getDisplayMetrics());
}
}
activity_main.xml布局文件如下,直接引用即可:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jackchen.view_day07_2.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="A"
android:textSize="16sp"
android:textColor="#FF0000"
android:id="@+id/letter_tv"
android:visibility="gone"
/>
<com.jackchen.view_day07_2.LetterSlideBar
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:id="@+id/letter_slide_bar"
/>
</RelativeLayout>
在MainActivity.java类中实现 触摸的接口,然后实现其方法,就会把当前触摸的字母及是否触摸传递给MainActivity,具体实现如下:
public class MainActivity extends AppCompatActivity implements LetterSlideBar.LetterTouchListener{
private TextView letter_tv;
private LetterSlideBar letter_slide_bar ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
letter_tv = (TextView) findViewById(R.id.letter_tv);
letter_slide_bar = (LetterSlideBar) findViewById(R.id.letter_slide_bar);
letter_slide_bar.setOnLetterTouchListener(this);
}
@Override
public void touch(CharSequence letter, boolean isTouch) {
if (isTouch){
letter_tv.setVisibility(View.VISIBLE);
letter_tv.setText(letter);
}else{
letter_tv.setVisibility(View.GONE);
}
}
}
具体代码已上传至github:
https://github.com/shuai999/View_day07_2.git