说到自定义Veiw,网上有很多但是不全,今天我就来和你们分享一下有那些自定义Veiw,在了解之前我希望你能了解View的事件分发和工作流程,推荐几篇文章给你们看看
View事件分发
View工作原理
目录
- 共同点
- 继承系统控件的自定义Veiw
- 继承View的自定义View
- 自定义组合控件(一般是多个控件的组合,继承布局)
- 自定义ViewGroup
- 真实总结
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));
}
}
总结
其实这些博客不太好理解(都特么是代码谁看),我个人纯粹是拿来当笔记,这些确实是我自己手敲,是我看书敲的,怕我自己不太理解就写一下博客加深记忆,如果有大佬看到不足之处,望指出。最近不知道怎么了,感觉心浮气躁的,学习也没什么精神,但是我希望在下一站我能调整一下,把多线程学好了,认真分享给大家。