Android 自定义View

说到自定义Veiw,网上有很多但是不全,今天我就来和你们分享一下有那些自定义Veiw,在了解之前我希望你能了解View的事件分发和工作流程,推荐几篇文章给你们看看
View事件分发
View工作原理
在这里插入图片描述

目录
  1. 共同点
  2. 继承系统控件的自定义Veiw
  3. 继承View的自定义View
  4. 自定义组合控件(一般是多个控件的组合,继承布局)
  5. 自定义ViewGroup
  6. 真实总结
1.共同点

嘿嘿,不知道了吧。共同点就是他们会遵从一定的规则,在系统控件满足的情况下,还是别使用自定义View,一下子出了Bug,你锅往哪里丢,又不像浏览器的500,浏览器500就是后台人员的错误了
在这里插入图片描述

2.继承系统控件的自定义Veiw

我知道,我懂,你们是面向百度的CV工程师,看不到代码都是虚的,那我直接上代码了

package com.example.syt.myapplication;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by syt on 2019/3/19.
 */

@SuppressLint("AppCompatCustomView")
public class InvalidTextView extends TextView{
	//创建一个小画笔
    private Paint mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
    public InvalidTextView(Context context) {
        super(context);
        initDraw();
    }

    public InvalidTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initDraw();
    }

    public InvalidTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initDraw();
    }
	//设置画笔颜色和填充宽度
    private void initDraw(){
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth((float)1.5);
    }
	//随便画
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width=getWidth();
        int height=getHeight();
        canvas.drawLine(0,height/2,width,height/2,mPaint);
    }
}

在看看我们的布局引用

<com.example.syt.myapplication.InvalidTextView
    android:gravity="center"
    android:text="啦啦啦"
    android:background="#2ebdff"
    android:layout_width="match_parent"
    android:layout_height="100dp" />

在这里插入图片描述

3. 继承View的自定义View

这时候你就要对系统属性进行处理了,不然你的自定义View是没效果的哦

android:layout_width=""
android:layout_height=""
android:padding=""
//只需在onDraw()方法和onMeasure()方法中编写代码就行,是不是懵逼了,这就要牵扯到View的工作流程了,看看我上面推荐的文章呗
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    int paddingLeft=getPaddingLeft();
    int paddingRight=getPaddingRight();
    int paddingTop=getPaddingTop();
    int paddingBottom=getPaddingBottom();

    int width =getWidth()-paddingLeft-paddingRight;
    int height=getHeight()-paddingTop-paddingBottom;
    canvas.drawRect(paddingLeft,paddingTop,width+paddingLeft,height+paddingTop,mPaint);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
    int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
    int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
    if (widthSpecMode==MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST){
        setMeasuredDimension(600,600);
    }else if(widthSpecMode==MeasureSpec.AT_MOST){
        setMeasuredDimension(600,heightSpecSize);
    }else if(heightSpecMode==MeasureSpec.AT_MOST){
        setMeasuredDimension(widthSpecSize,600);
    }
}

在看看我们xml文件中的引用

<com.example.syt.myapplication.RectView
    android:layout_marginTop="50dp"
    android:layout_centerHorizontal="true"
    android:id="@+id/rv_rect"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="20dp"
    app:rect_color="@android:color/holo_blue_light"
    />

效果图

在这里插入图片描述
当然啦,这里也有我们的自定义属性
在values中创建attrs.xml文件就行

<?xml version="1.0" encoding="utf-8"?>
<resources>
	//name为自定义控件类名
    <declare-styleable name="RectView">
    	//xml掉用属性为app:rect_color=""
    	//前提是你得导入xmlns:app="http://schemas.android.com/apk/res-auto"
        <attr name="rect_color" format="color"/>//format可以多个或者一个属性值得填写我给链接大家看看
    </declare-styleable>
</resources>

format取值类型
在看看我们的RectView中的调用

public RectView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    TypedArray mTypedArray=context.obtainStyledAttributes(attrs,R.styleable.RectView);
    mColor=mTypedArray.getColor(R.styleable.RectView_rect_color,Color.RED);
    mTypedArray.recycle();//用完清楚就行
    initDarw();
}
4. 自定义组合控件(一般是多个控件的组合,继承布局)

这个我觉得先上效果图
在这里插入图片描述
在这里插入图片描述
这是两张图片和text文本实现组合控件
看看我们的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rl_titlebar_rootlayout"
    android:layout_width="match_parent"
    android:layout_height="45dp">
    <ImageView
        android:id="@+id/iv_titlebar_left"
        android:src="@drawable/gr"
        android:layout_width="60dp"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_centerInParent="true"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        />
    <TextView
        android:id="@+id/tv_titlebar_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:maxEms="11"
        android:singleLine="true"
        android:ellipsize="end"
        android:textStyle="bold"
        />
    <ImageView
        android:gravity="center"
        android:padding="15dp"
        android:src="@drawable/gr"
        android:layout_alignParentRight="true"
        android:id="@+id/iv_titlebar_right"
        android:layout_width="60dp"
        android:layout_height="match_parent" />
</RelativeLayout>

在看看我们的自定义控件类
和自定义属性文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TitleBar">
        <attr name="title_text_color" format="color"/>
        <attr name="title_bg" format="color"/>
        <attr name="title_text" format="string"/>
    </declare-styleable>
</resources>
package com.example.syt.myapplication;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

/**
 * Created by syt on 2019/3/19.
 */

public class TitleBar extends RelativeLayout{

    private ImageView iv_titlebar_left;
    private ImageView iv_titlebar_right;
    private TextView tv_titlebar_title;
    private RelativeLayout rl_titlebar_rootlayout;

    private int mColor= Color.BLUE;
    private int mTextColor= Color.WHITE;
    private String titlename;


    public TitleBar(Context context) {
        super(context);
    }

    public TitleBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        //自定义属性加载
        TypedArray mTypedArray=context.obtainStyledAttributes(attrs,R.styleable.TitleBar);
        mColor=mTypedArray.getColor(R.styleable.TitleBar_title_bg,Color.BLUE);
        mTextColor=mTypedArray.getColor(R.styleable.TitleBar_title_text_color,Color.WHITE);
        titlename = mTypedArray.getString(R.styleable.TitleBar_title_text);

        mTypedArray.recycle();
        initView(context);
    }

    public TitleBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public void initView(Context context){
        LayoutInflater.from(context).inflate(R.layout.layout_title_bar,this,true);
        iv_titlebar_left = (ImageView)findViewById(R.id.iv_titlebar_left);
        iv_titlebar_right = (ImageView)findViewById(R.id.iv_titlebar_right);
        tv_titlebar_title = (TextView)findViewById(R.id.tv_titlebar_title);
        rl_titlebar_rootlayout = (RelativeLayout)findViewById(R.id.rl_titlebar_rootlayout);
        //设置背景颜色
        rl_titlebar_rootlayout.setBackgroundColor(mColor);
        tv_titlebar_title.setTextColor(mTextColor);
        setTitle(titlename);
    }
    public void setTitle(String titlename){
        if (!TextUtils.isEmpty(titlename)){
            tv_titlebar_title.setText(titlename);
        }

    }
    //设置点击事件接口
    public void setLeftListener(OnClickListener onClickListener){
        iv_titlebar_left.setOnClickListener(onClickListener);
    }
    public void setRightListener(OnClickListener onClickListener){
        iv_titlebar_right.setOnClickListener(onClickListener);
    }
}

在看看我们activity_mian的引用

<?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.example.syt.myapplication.MainActivity">
    <com.example.syt.myapplication.TitleBar
        android:id="@+id/tb_title"
        app:title_text_color="@android:color/holo_blue_dark"
        app:title_bg="@android:color/holo_orange_dark"
        android:layout_width="match_parent"
        app:title_text="自定义组合控件"
        android:layout_height="45dp">

    </com.example.syt.myapplication.TitleBar>
</RelativeLayout>

最后在看看我们的activity中的实现

TitleBar tb_title=(TitleBar) findViewById(R.id.tb_title);
tb_title.setLeftListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(MainActivity.this, "点击左键", Toast.LENGTH_SHORT).show();
    }
});
tb_title.setRightListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(MainActivity.this, "点击右键", Toast.LENGTH_SHORT).show();
    }
});
5. 自定义ViewGroup

最最最最 nb的点来了,请认真
在这里插入图片描述
付效果图:可侧滑的哟不会冲突哦
在这里插入图片描述
1.创建类继承ViewGroup

public class HorizontalView extends ViewGroup{
    private int lastInterceptX;
    private int lastInterceptY;
    private int lastX;
    private int lastY;
    private int currentIndex=0;
    private int childWidth=0;
    private Scroller scroller;
    private VelocityTracker tracker;

    public HorizontalView(Context context) {
        super(context);
        init();
    }

    public HorizontalView(Context context, AttributsseSet attrs) {
        super(context, attrs);
        init();
    }

    public HorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount=getChildCount();
        int left=0;
        View child;
        for(int i=0;i<childCount;i++){
            child=getChildAt(i);
            if (child.getVisibility()!=View.GONE){
                int width=child.getMeasuredWidth();
                childWidth=width;
                child.layout(left,0,left+width,child.getMeasuredHeight());
                left+=width;
            }
        }
    }
}

2.对wrap_content属性进行处理
在HorizontalView中重写onMeasure()方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthMode=MeasureSpec.getMode(widthMeasureSpec);
    int widthSize=MeasureSpec.getSize(widthMeasureSpec);
    int heightMode=MeasureSpec.getMode(heightMeasureSpec);
    int heightSize=MeasureSpec.getSize(heightMeasureSpec);

    measureChildren(widthMeasureSpec,heightMeasureSpec);
    //如果没有子元素,就设置宽和高都为0
    if (getChildCount()==0){
        setMeasuredDimension(0,0);
    }
    //高和宽都是AT_MOST,则宽度设置为所有子元素宽度的和,高度设置为第一个子元素的高度
    else if (widthMode==MeasureSpec.AT_MOST&&heightMode==MeasureSpec.AT_MOST){
        View childOne=getChildAt(0);
        int childWidth=childOne.getMeasuredWidth();
        int childHeight=childOne.getMeasuredHeight();
        setMeasuredDimension(childWidth*getChildCount(),childHeight);
    }
    //宽度是AT_MOST,则宽度为所有子元素的和
    else if (widthMode==MeasureSpec.AT_MOST){
        int childWidth=getChildAt(0).getMeasuredWidth();
        setMeasuredDimension(childWidth*getChildCount(),heightSize);
    }
    //高度是AT_MOST,则高度为第一个子元素的高度
    else if (heightMode==MeasureSpec.AT_MOST){
        int childHeight=getChildAt(0).getMeasuredHeight();
        setMeasuredDimension(widthSize,childHeight);
    }
}

3.处理弹性滑动以及滑动到其他界面,需要重写onInterceptTouchEvent(),onTouchEvent()

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercept=false;
    int x=(int)ev.getX();
    int y=(int)ev.getY();
    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            intercept=false;
            if (scroller.isFinished()){
                scroller.abortAnimation();
            }
            break;
        case MotionEvent.ACTION_MOVE:
            int deltaX=x-lastInterceptX;
            int deltaY=y-lastInterceptY;
            if (Math.abs(deltaX)-Math.abs(deltaY)>0){
                intercept=true;
            }else {
                intercept=false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercept=false;
            break;
    }
    lastX=x;
    lastY=y;
    lastInterceptX=x;
    lastInterceptY=y;

    return intercept;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    int x=(int)event.getX();
    int y=(int)event.getY();
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            if (!scroller.isFinished()){
                scroller.abortAnimation();
            }
            break;
        case MotionEvent.ACTION_MOVE:
            int deltaX=x-lastX;
            scrollBy(-deltaX,0);
            break;
        case MotionEvent.ACTION_UP:
            int distance=getScrollX()-currentIndex*childWidth;
            if (Math.abs(distance)>childWidth/2){
                if (distance>0){
                    currentIndex++;
                }else {
                    currentIndex--;
                }
            }else {
                tracker.computeCurrentVelocity(1000);
                float xV=tracker.getXVelocity();
                if (Math.abs(xV)>50){
                    currentIndex--;
                }else {
                    currentIndex++;
                }
            }
            currentIndex=currentIndex<0?0:currentIndex>getChildCount()-1?getChildCount()-1:currentIndex;

            smoothScrollTo(currentIndex*childWidth,0);
            tracker.clear();
            break;
            default:
                break;
    }
    lastX=x;
    lastY=y;
    return true;
}

@Override
public void computeScroll() {
    super.computeScroll();
    if (scroller.computeScrollOffset()){
        scrollTo(scroller.getCurrX(),scroller.getCurrY());
        postInvalidate();
    }
}

private void smoothScrollTo(int i, int i1) {
    scroller.startScroll(getScrollX(),getScrollY(),i-getScrollX(),i1-getScrollY(),1000);
    invalidate();
}
private void init(){
    scroller=new Scroller(getContext());
    tracker=VelocityTracker.obtain();
}

其中有两个重要的点
_1.滑动到其他界面会用到Scroller
_2.快速滑动会用到VelocityTracker

4.布局的引用

<?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.example.syt.myapplication.MViewActivity">
    <com.example.syt.myapplication.HorizontalView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ListView
            android:id="@+id/lv_info1"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </ListView>
        <ListView
            android:id="@+id/lv_info2"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </ListView>
    </com.example.syt.myapplication.HorizontalView>
</RelativeLayout>

5.在看看我们activity中的实现

package com.example.syt.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MViewActivity extends AppCompatActivity {

    private ListView lv_info1;
    private ListView lv_info2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mview);
        lv_info1 = (ListView) this.findViewById(R.id.lv_info1);
        lv_info2 = (ListView) this.findViewById(R.id.lv_info2);
        String[] strs1={"1","2","3","4","5","6","7","8","9","10"};
        lv_info1.setAdapter(new ArrayAdapter<String>(this,R.layout.list_info_item,strs1));
        String[] strs2={"A","B","C","D","E","F","G","H","I","J"};
        lv_info2.setAdapter(new ArrayAdapter<String>(this,R.layout.list_info_item,strs2));
    }
}
总结

其实这些博客不太好理解(都特么是代码谁看),我个人纯粹是拿来当笔记,这些确实是我自己手敲,是我看书敲的,怕我自己不太理解就写一下博客加深记忆,如果有大佬看到不足之处,望指出。最近不知道怎么了,感觉心浮气躁的,学习也没什么精神,但是我希望在下一站我能调整一下,把多线程学好了,认真分享给大家。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值