前言
在我们的日常开发当中,当Android原生控件满足不了我们业务需求的时候,我们就应该使用自定义View去解决问题,话说自定义View真的很难,一些复杂的自定义View里面涉及的算法非常之多,所以往往我们会从网上去找别人实现好的自定义View,然后做相应的修改
自定义View的分类
- 自定义组合控件(继承一个Android已有的布局,里面有填充了子控件)
- 自定义拓展控件(继承一个Android已有的View,例如:ImageView,TextView等等)
- 完全自定义View(继承View或者ViewGroup)
自定义View的步骤(以继承View为例)
-
创建一个类继承View,重写三个构造方法,如下图:
第一步
- 第一个构造方法:允许我们以代码的形式去创建自定义View
- 第二个构造方法:允许我们以XML布局的形式创建自定义View
- 第三个构造方法: 允许我们以XML布局的形式创建自定义View,并且可以为自定义View添加主题
- 自定义属性
-
通常我们会创建一个在value文件夹下创建一个assets.xml文件去存放我们自定义View的属性,然后在代码中引用,然后在第二个或者第三个构造方法中去获取自定义的属性,如下图:
创建
- 重写onDraw(),onMeasure(),onTouchEvent()等方法
- onDraw():draw是绘制的意思,这个方法就是用来绘制各种图形的
- onMeasure():用来测量自定义View的宽高
- onTouchEvent():用来处理自定义View中,手指按下,移动,抬起等等居多状态的逻辑.
通过上面3步我们就可以写出一个简单的自定义View了
下面看我实现的一个简单的自定义View:IndexBarView,功能实现了侧边快速导航栏,可以与RecyclerView进行联动:
/**
* 需求实现一个侧边快速导航栏
* Created by Administrator on 2018/3/26.
*/
public class IndexBarView extends View {
/**
* 数据源
*/
private List<String> dataList;
//画笔
private Paint textPaint;
//每个字母的高度
private int letterHeight;
//手指按下,移动,抬起的接口回调
private OnIndexListenr onIndexListenr;
//当前字母的下标
int index = -1;
//标志位 手指是否抬起
private boolean isUp;
public IndexBarView(Context context) {
this(context, null);
}
public IndexBarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
TypedArray typedArray = getResources().obtainAttributes(attrs, R.styleable.IndexBarView);
float textSize = typedArray.getDimension(R.styleable.IndexBarView_textSize, 0);
typedArray.recycle();
}
public IndexBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void setOnIndexListenr(OnIndexListenr onIndexListenr) {
this.onIndexListenr = onIndexListenr;
}
/**
* 初始化
*/
private void init() {
dataList = new ArrayList<>();
dataList.add("A");
dataList.add("B");
dataList.add("C");
dataList.add("D");
dataList.add("E");
dataList.add("F");
dataList.add("G");
dataList.add("H");
dataList.add("I");
dataList.add("J");
dataList.add("K");
textPaint = new Paint();
textPaint.setColor(Color.RED);
textPaint.setTextSize(50);
textPaint.setAntiAlias(true);
letterHeight = (int) (textPaint.descent() - textPaint.ascent());
}
/**
* 绘制图像
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < dataList.size(); i++) {
if(!isUp){
if(index == i){
textPaint.setColor(Color.BLACK);
}else {
textPaint.setColor(Color.RED);
}
}else {
textPaint.setColor(Color.RED);
}
canvas.drawText(dataList.get(i), getWidth() / 2 - textPaint.measureText(dataList.get(i)) / 2, letterHeight * (i + 1), textPaint);
}
}
/**
* 测量宽高
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureSpec(widthMeasureSpec, 0), measureSpec(heightMeasureSpec, 1));
}
/**
* 测量宽高封装的一个方法
* @param measureSpec
* @param type
* @return
*/
private int measureSpec(int measureSpec, int type) {
int mode = MeasureSpec.getMode(measureSpec);
int maxSize = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.EXACTLY:
return maxSize;
case MeasureSpec.AT_MOST:
switch (type) {
case 0:
return (int) (textPaint.measureText(dataList.get(0)) + getPaddingLeft() + getPaddingRight());
case 1:
return letterHeight * dataList.size() + getPaddingTop() + getPaddingBottom();
}
break;
}
return 0;
}
/**
* 设置数据源
* @param datas
*/
public void setDatas(List<String> datas){
this.dataList = datas;
invalidate();
}
/**
* 处理手指按下,移动,抬起的逻辑
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
downAndMoveEvent(event);
break;
case MotionEvent.ACTION_UP:
isUp = true;
index = -1;
if(onIndexListenr != null){
onIndexListenr.onUpListener();
}
break;
}
invalidate();
return true;
}
/**
* 手指移动和抬起,逻辑相同
* @param event
*/
private void downAndMoveEvent(MotionEvent event) {
int y = (int) event.getY();
int in = y/letterHeight;
if(in < 0){
in = 0;
}
if(in > dataList.size() - 1){
in = dataList.size() -1;
}
if(index != in){
index = in;
}else {
return;
}
if(onIndexListenr != null){
onIndexListenr.onDownAndMoveListener(in,dataList.get(in));
}
isUp = false;
}
/**
* 接口回调
*/
public interface OnIndexListenr{
void onDownAndMoveListener(int position,String text);
void onUpListener();
}
}
配合如下代码:
Shape资源
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp"/>
<solid android:color="#000"/>
</shape>
item_pop_center.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:background="@drawable/shpae_background_black"
android:layout_width="100dp"
android:layout_height="100dp">
<TextView
android:id="@+id/tv_pop"
android:textColor="#fff"
android:textSize="50sp"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private IndexBarView indexBarView;
private PopupWindow popupWindow;
private TextView tvPop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
indexBarView = findViewById(R.id.index_bar_view);
View popView = LayoutInflater.from(this).inflate(R.layout.item_pop_center,null);
tvPop = popView.findViewById(R.id.tv_pop);
popupWindow = new PopupWindow(popView, ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT,true);
indexBarView.setOnIndexListenr(new IndexBarView.OnIndexListenr() {
@Override
public void onDownAndMoveListener(int position, String text) {
tvPop.setText(text);
if(!popupWindow.isShowing()){
popupWindow.showAtLocation(indexBarView, Gravity.CENTER,0,0);
}
}
@Override
public void onUpListener() {
if(popupWindow.isShowing()){
popupWindow.dismiss();
}
}
});
}
}
便可实现如下效果:
下面附上一篇学自定义View比较好的文章传送门,大家有时间可以去看看,写的很好
网友评论