博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 自定义View
阅读量:6572 次
发布时间:2019-06-24

本文共 9220 字,大约阅读时间需要 30 分钟。

hot3.png

这篇文章紧接着上面两篇文章(Android事件机制和Android View的工作原理来说明自定义View的一些注意事项)下面通过两个例子来说明自定义view的过程。

  • CircleView 绘制一个圆形的View
  • ScrollView绘制一个可以滑动的列表

CircleView

1.Circle可以做到的事情:支持padding属性,支持wrap_content布局模式,在指定的地点绘制一个圆,支持app:color属性,用来指定view的背景颜色,效果图

2.预备知识点

  • Circle继承自View
  • 为了支持wrap_content需要自己重写onMeasure方法,因为,wrap_content模式下,要测量的时候,父元素传进来的是AT_MOST模式和parentSize,如果不处理就和match_parent模式一样,所以需要设置一个默认的大小,当为wrap_content的时候设置为这个大小
  • onMeasure的重写方法,需要分解出下面两个值,根据值来确定measure的大小,测量完成之后要用setMeasureDimension方法来设置值的大小
    MeasureSpec.getMode
    MeasureSpec.getSize
  • padding属性是和具体的子元素的布局有关,因此在View基类里面没有实现,为了支持这个属性需要自己设置大小(在绘制的时候设置大小)
  • 为了支持自定义属性,需要接收AttributeSet来获得这些属性值,也要声明自己的命名前缀

3.代码实现

3.1CircleView的主文件

import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;/** * Created by kisstheraik on 16/8/8. * Description 一个圆形的view支持大小变化和颜色变化 */public class CircleView extends View {    private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);    private int color=Color.RED;    public CircleView(Context context){        super(context);    }    public CircleView(Context context,AttributeSet attrs){       this(context,attrs,0);    }    public CircleView(Context context,AttributeSet attributeSet,int defStyle){        super(context,attributeSet,defStyle);        TypedArray list=context.obtainStyledAttributes(attributeSet,R.styleable.CircleView);        color=list.getColor(R.styleable.CircleView_color,Color.RED);//解析出自定义的color属性//这里需要回收资源        list.recycle();    }    @Override    protected void onDraw(Canvas canvas){        //处理padding        int paddingLeft=getPaddingLeft();        int paddingRight=getPaddingRight();        int paddingTop=getPaddingTop();        int paddingBottom=getPaddingBottom();        int width=getWidth()-paddingLeft-paddingRight;        int height=getHeight()-paddingBottom-paddingTop;        int radius=Math.min(width, height)/2;        paint.setColor(color);        //画出圆        canvas.drawCircle(width / 2 + paddingLeft, height / 2 + paddingTop, radius, paint);    }    //为了支持wrap_content模式,重写measure方法    @Override    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int wmode=MeasureSpec.getMode(widthMeasureSpec);        int hmode=MeasureSpec.getSize(heightMeasureSpec);        int wsize=MeasureSpec.getMode(widthMeasureSpec);        int hsize=MeasureSpec.getSize(heightMeasureSpec);//根据不同的模式设置不同的值        if(wmode==MeasureSpec.AT_MOST&&hmode==MeasureSpec.AT_MOST){            setMeasuredDimension(200,200);        }else if(wmode==MeasureSpec.AT_MOST){            setMeasuredDimension(200,hsize);        }else if(hmode==MeasureSpec.AT_MOST){            setMeasuredDimension(wsize,200);        }    }    public void setColor(int color){        this.color=color;//可以在代码里面修改view的颜色        invalidate();    }}

3.2xml布局文件

ScrollView

1.scrollView可以做到的事情,scrollview可以里面添加子元素,每个子元素会横向填充整个屏幕,然后能够实现子元素之间水平滑动,子元素内部也可以滑动,比如子元素是一个listview,这样可以实现一个自定义的ViewPager。效果图

2.预备知识点

  • 什么是滑动冲突和怎么解决,滑动冲突就是在有多个元素的时候,事件不能正确的传递到要处理事件的view,导致不能正常滑动,比如一个可以横向滑动的view的里面有可以纵向滑动的list,这时候滑动就不知道该滑动哪一个等等。滑动冲突的解决方式主要是自己定制TouchEvent的分发机制,可以在父元素拦截事件确定分发的逻辑,也可以在子元素干扰父元素的事件分发来达到相同的效果,ScrollView采用的策略是在父元素里面写分发的逻辑
  • 利用scroller进行滑动,滑动的逻辑实际上构成了一个可以退出的循环  onDraw->computeScroll->invalidate->onDraw  然后每次在computeScroll里面计算需要滑动到的位置,进行滑动,scroller里面就保存了整个滑动序列,这个序列会随时间变化,具体来说就是记录了滑动是否完成,和下一次需要滑到的地方。
  • 事件分发的处理,这里需要从功能角度确定事件分发,具体的逻辑会在代码里面说明
  • 布局的处理,onLayout里面进行子元素的布局,根据measure的尺寸进行子元素的布局

3.代码

3.1主代码

import android.content.Context;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewGroup;import android.widget.Scroller;/** * Created by kisstheraik on 16/8/8. * Description 可以左右滑动的view */public class ScrollView extends ViewGroup {    private Scroller scroller;    private VelocityTracker velocityTracker;//用来测量速度    private int lastX=0;    private int lastY=0;    private int childIndex=0;    private int childWidth=0;    private int childSize=0;    public ScrollView(Context context){        super(context);        init();    }    public ScrollView(Context context,AttributeSet attributeSet){        super(context,attributeSet);        init();    }    public ScrollView(Context context,AttributeSet attributeSet,int def){        super(context,attributeSet,def);        init();    }    public void init(){        if(scroller==null) {            scroller = new Scroller(getContext());            velocityTracker=VelocityTracker.obtain();        }    }    //是否拦截某个事件,这个元素是父元素,使用外部拦截的方式来解决滑动冲突    @Override    public boolean onInterceptTouchEvent(MotionEvent ev){        boolean inter=false;        int x=(int)ev.getX();        int y=(int)ev.getY();        switch (ev.getAction()){            case MotionEvent.ACTION_DOWN:                inter=false;                //当还在滑动的时候就拦截按下的事件,然后退出滑动                if(!scroller.isFinished()){                    scroller.abortAnimation();                    inter=true;                }                break;            case MotionEvent.ACTION_MOVE:                int delX=x-lastX;                int delY=y-lastY;                if(Math.abs(delX)>Math.abs(delY)){//当x方向的滑动距离更大,说明还要继续滑动                    inter=true;                }else inter=false;                break;            case MotionEvent.ACTION_UP://不拦截这个事件,但是子元素处理不了的时候会调用父元素的onTouchEvent方法                inter=false;                break;            default: inter=false;                break;        }        lastY=y;        lastX=x;        return inter;    }    //处理接收到的事件    @Override    public boolean onTouchEvent(MotionEvent ev){        velocityTracker.addMovement(ev);        int x=(int)ev.getX();        int y=(int)ev.getY();        switch (ev.getAction()){            case MotionEvent.ACTION_DOWN:                if(!scroller.isFinished()){                    scroller.abortAnimation();                }                break;            case MotionEvent.ACTION_MOVE:                int dex=x-lastX;                scrollBy(-dex,0);//这里是跟着手指在移动,因此需要瞬移                break;            case MotionEvent.ACTION_UP:                //需要在停下来的时候滑动到最近的child的边界上                int sx=getScrollX();                velocityTracker.computeCurrentVelocity(1000);                float xv=velocityTracker.getXVelocity();                if(Math.abs(xv)>=50){//速度达到一定的值就跳到下一个元素                    childIndex=xv>0?childIndex-1:childIndex+1;                }else {                    childIndex=(sx+childWidth/2)/childWidth;//否则看看哪一个元素占用的比例多                }                childIndex=Math.max(0,Math.min(childIndex,childSize-1));                int dx=childIndex*childWidth-sx;                smoothscroolBy(dx,0);//然后滑动到那里                velocityTracker.clear();                break;            default:break;        }        lastX=x;        lastY=y;        return true;    }    private void smoothscroolBy(int dx,int dy){        scroller.startScroll(getScrollX(),0,dx,0,500);        invalidate();    }    @Override    public void computeScroll(){        if(scroller.computeScrollOffset()){            scrollTo(scroller.getCurrX(),scroller.getCurrY());            postInvalidate();        }    }    //布局已经完成    @Override    protected void onLayout(boolean changed,int l,int t,int r,int b){        final int count=getChildCount();        int childLeft=0;        childSize=count;        childWidth=getChildAt(0).getWidth();        for(int i=0;i

3.2使用代码

布局:

代码:

scrollView=(ScrollView)findViewById(R.id.myscroll);        LayoutInflater layoutInflater=getLayoutInflater();        WindowManager wm = (WindowManager) getApplication()                .getSystemService(Context.WINDOW_SERVICE);        for (int i = 0; i < 3; i++) {            ViewGroup layout = (ViewGroup) layoutInflater.inflate(                    R.layout.scroll_view_content, scrollView, false);            layout.getLayoutParams().width = wm.getDefaultDisplay().getWidth();            TextView textView = (TextView) layout.findViewById(R.id.title);            textView.setText("page " + (i + 1));            createList(layout);            scrollView.addView(layout);        } private void createList(ViewGroup layout) {        ListView listView = (ListView) layout.findViewById(R.id.list);        ArrayList
datas = new ArrayList
(); for (int i = 0; i < 50; i++) { datas.add("name " + i); } ArrayAdapter
adapter = new ArrayAdapter
(this, android.R.layout.simple_list_item_1,datas); listView.setAdapter(adapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView
parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "click item", Toast.LENGTH_SHORT).show(); } }); }

子元素的布局:

 

转载于:https://my.oschina.net/lovezfy/blog/729971

你可能感兴趣的文章
转: maven进阶:一个多模块项目
查看>>
Android控件之HorizontalScrollView 去掉滚动条
查看>>
UVM中的class--2
查看>>
关于异常的合理处理方式
查看>>
javascript ES3小测试
查看>>
Android - Animation(二)
查看>>
Android6.0指纹识别开发
查看>>
Lucene简介
查看>>
Hibernate概述
查看>>
任务调度器配置文件
查看>>
【JavaScript吉光片羽】--- 滑动条
查看>>
ORACLE 存储过程异常捕获并抛出
查看>>
arcgis api for js之echarts开源js库实现地图统计图分析
查看>>
Microsoft JDBC Driver 4.0 for SQL Server
查看>>
delphi2010中FastReport的安装方法
查看>>
总结新浪friendship接口
查看>>
HDU 4293 Groups (线性dp)
查看>>
博客园博客美化相关文章目录
查看>>
excel中如何批量将所有的网址设为超链接
查看>>
Nodejs学习笔记(十二)--- 定时任务(node-schedule)
查看>>