美文网首页
从零开始-重新认识自定义View

从零开始-重新认识自定义View

作者: sweetying | 来源:发表于2018-03-27 01:06 被阅读22次

    前言

    在我们的日常开发当中,当Android原生控件满足不了我们业务需求的时候,我们就应该使用自定义View去解决问题,话说自定义View真的很难,一些复杂的自定义View里面涉及的算法非常之多,所以往往我们会从网上去找别人实现好的自定义View,然后做相应的修改

    自定义View的分类

    • 自定义组合控件(继承一个Android已有的布局,里面有填充了子控件)
    • 自定义拓展控件(继承一个Android已有的View,例如:ImageView,TextView等等)
    • 完全自定义View(继承View或者ViewGroup)

    自定义View的步骤(以继承View为例)

    1. 创建一个类继承View,重写三个构造方法,如下图:


      第一步
    • 第一个构造方法:允许我们以代码的形式去创建自定义View
    • 第二个构造方法:允许我们以XML布局的形式创建自定义View
    • 第三个构造方法: 允许我们以XML布局的形式创建自定义View,并且可以为自定义View添加主题
    1. 自定义属性
    • 通常我们会创建一个在value文件夹下创建一个assets.xml文件去存放我们自定义View的属性,然后在代码中引用,然后在第二个或者第三个构造方法中去获取自定义的属性,如下图:


      创建
    引用
    1. 重写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比较好的文章传送门,大家有时间可以去看看,写的很好

    相关文章

      网友评论

          本文标题:从零开始-重新认识自定义View

          本文链接:https://www.haomeiwen.com/subject/hcodcftx.html