美文网首页自定义view相关安卓自定义ViewAndroid知识
Android难点之——自定义View(中)

Android难点之——自定义View(中)

作者: JuliusL | 来源:发表于2016-06-11 22:12 被阅读635次

    注:以下笔记都是通过《Android群英传》这本书整理而来。

    〇、自定义View前篇

    1、我们不能机械地记忆所有绘图的API,而是要让这些API为你所用,结合现实中的绘图方法,甚至是Photoshop的技巧,才能设计出更好的自定义View。
    2、一个用户觉得熟悉的控件才是好的控件,切记勿追求华而不实。
    3、View中比较重要的回调方法:

    • onFinishInflate():从XML加载组件后回调。
    • onSizeChanged():组件大小改变时。
    • onMeasure():回调该方法来进行测量。
    • onLayout():回调该方法来确定显示的位置。
    • onTouchEvent():监听到触摸事件时回调。

    一、对现有控件进行扩展

    案例一:

    1.png
    part1:在构造中创建两支画笔,一支画背景一支画边框。
    mPaint1 = new Paint();
    mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
    mPaint1.setStyle(Paint.Style.FILL);// 设置风格为实心
    
    mPaint2 = new Paint();
    mPaint2.setColor(Color.YELLOW);
    mPaint2.setStyle(Paint.Style.FILL);
    

    part2:在onDraw方法中绘制。

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint1);
        //绘制内层矩形
        canvas.drawRect(10,10,getMeasuredWidth()-10,getMeasuredHeight()-10,mPaint2);
        canvas.save();
        //绘制文字前平移10像素
        canvas.translate(10,10);
        //在回调父方法前,实现自己的逻辑,对TextView来说就是在绘制文本内容前
        super.onDraw(canvas);
        canvas.restore();
    }
    

    案例二:

    闪动的文字效果.png
    part1:
    1、在onSizeChanged中初始化一些对象,是因为在该方法中组件的大小才真正改变,才能拿到组件的长宽被这些对象所用。
    2、利用Paint对象的Shader渲染器,通过设置一个不断变化的LinearGradient,并使用带该属性(渲染器)的Paint来绘制要显示的文字。
    3、最关键的就是使用getPaint()方法获取当前绘制TextView的Paint对象,并给Paint设置原生TextView没有的LinearGradient属性。
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if(mViewWidth == 0){
            mViewWidth = getMeasuredWidth();
            if(mViewWidth>0){
                mPaint = getPaint(); // 注意这个地方不要写成new Paint() 
                //参数一为渐变起初点坐标x位置,参数二为y轴位置,参数三和四分辨对应渐变终点,
                //参数五是参与渐变效果的颜色集合
                //参数六是定义每个颜色处于的渐变相对位置,这个参数可以为null,如果为null表示所有的颜色按顺序均匀的分布
                //最后参数为平铺方式
                mLinearGradient = new LinearGradient(
                        0,0,mViewWidth,0,new int[]{
                        Color.BLUE,0xffffffff,Color.BLUE
                },null, Shader.TileMode.CLAMP);
                mPaint.setShader(mLinearGradient);
                mGradientMatrix = new Matrix();
            }
        }
    }
    

    part2:通过矩阵的方式来不断平移渐变效果,在绘制文字时,产生动态的闪动效果。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mGradientMatrix!=null){
            mTranslate += mViewWidth/10;
            if(mTranslate > 2 * mViewWidth){
                mTranslate = -mViewWidth;
            }
            mGradientMatrix.setTranslate(mTranslate,0);
            mLinearGradient.setLocalMatrix(mGradientMatrix);
            postInvalidateDelayed(50);
        }
    }
    

    二、创建复合控件

    书上的案例太简单,代码有点多这里就不贴了。
    简单总结一下就是分为四个步骤:
    1、自定义属性

    1、在res资源目录下values目录下创建attrs.xml属性定义文件。

    <resource>
        <declare-styleable name="一般这里写控件的名字">
            <attr name="属性名" format="属性类型,可以用"|"分隔不同的属性"/>
        </declare-styleable>
    </resouce>
    

    2、在构造中用代码获取xml布局中自定义的那些属性。

    TypeArray ta = context.obtainStyledAttributes(attr,R.styleable.控件名);
    属性类型 属性值 = ta.get属性类型(R.styleable.属性名);
    ta.recycler();//调用recycler完成资源回收
    

    2、组合控件

    1、为创建的组件元素赋值,值就来源于我们在引用的xml文件中给对应属性的赋值。

    Button btn1 = new Button(context);
    btn1.setTextColor(属性值);
    

    2、为组件元素设置响应的布局元素。

    LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
    params .addRule(ALIGN_PARENT_RIGHT,TRUE);
    

    3、添加到ViewGroup。

    addView(btn1,params);
    

    3、定义接口。(略过)
    4、暴露接口给调用者。(略过)

    三、重写View来实现全新的控件

    案例三:

    案例三.png
    part1:在构造方法中初始化对象。
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    int screenWidth = wm.getDefaultDisplay().getWidth();
    //圆心
    mCircleXY = screenWidth/2;
    //圆的半径
    mRadius = (float) (screenWidth*0.5/2);
    //指定圆弧的外轮廓矩形区域
    mArcRectF = new RectF((float)(screenWidth*0.1),(float)(screenWidth*0.1),(float)(screenWidth*0.9),(float)(screenWidth*0.9));
    mCirclePaint = new Paint();
    mCirclePaint.setColor(Color.BLUE);
    mArcPaint = new Paint();
    mArcPaint.setStyle(Paint.Style.STROKE);
    mArcPaint.setStrokeWidth(50);
    mArcPaint.setColor(Color.GREEN);
    mTextPaint = new Paint();
    mTextPaint.setColor(Color.BLACK);
    mShowText = "哈哈哈哈";
    mShowTextSize = 40;
    

    part2:分别绘制圆、圆弧、文字。

    @Override
    protected void onDraw(Canvas canvas) {
        //绘制圆
        canvas.drawCircle(mCircleXY,mCircleXY,mRadius,mCirclePaint);
        //绘制弧线(
        //  参数1:指定圆弧的外轮廓矩形区域,
        //  参数2:圆弧起始角度,单位为度
        //  参数3:圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度
        //  参数4:如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形
        //  参数5:绘制圆弧的画板属性,如颜色,是否填充等。)
        canvas.drawArc(mArcRectF,270,270,false,mArcPaint);
        //绘制文字
        canvas.drawText(mShowText,0,mShowText.length(),mCircleXY,mCircleXY+(mShowTextSize/4),mTextPaint);
    }
    

    相关资料:
    android中canvas.drawText参数的介绍以及绘制一个文本居中的案例
    Android的DrawText详解
    如何“任性”使用Android的drawText()
    drawArc方法介绍
    案例四:

    案例四.png
    part1:先在构造中初始化一些对象
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    mWidth = wm.getDefaultDisplay().getWidth();
    mPaint = new Paint();
    mPaint.setColor(Color.BLUE);
    

    part2:在组件改变位置的时候给Paint增加一个LinearGradient渐变效果。

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = getWidth();
        mRectHeight= getHeight();
        mRectWidth = (int)(mWidth*0.6/mRectCount);
        LinearGradient mLinearGradient = new LinearGradient(0,0,mRectWidth,mRectHeight,
                Color.YELLOW,Color.BLUE, Shader.TileMode.CLAMP);
        mPaint.setShader(mLinearGradient);
    }
    

    part3:在onDraw中循环绘制。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0;i<mRectCount;i++){
            double random = Math.random();
            //每个矩形的高
            float currentHeight = (float) (mRectHeight*random);
            canvas.drawRect((float)(mWidth*0.4/2+mRectWidth*i+offset),
                    currentHeight,(float)(mWidth*0.4/2+mRectWidth*(i+1)),
                    mRectHeight,mPaint);
        }
    }
    

    今天敲了一遍书上的案例,有些地方还不是很消化,例如绘制文字那块还是略模糊,明天继续更自定义ViewGroup。

    四、自定义ViewGroup

    1、ViewGroup的目的就是为了对其子View进行管理。
    2、ViewGroup通常要重写onMeasure()方法对子View进行测量,重写onLayout()确定子View的位置,重写onTouchEvent()增加响应事件。

    案例5:
    //TODO:待插入gif图
    part1:在构造函数中初始化一些对象:

    mScroller = new Scroller(context);
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    mScreenHeight = wm.getDefaultDisplay().getHeight();
    

    part2:在onMeasure中给子View进行测量。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = getChildCount();
        for (int i=0;i<count;i++){
            View childView = getChildAt(i);
            //用遍历的方式给子View测量
            measureChild(childView,widthMeasureSpec,heightMeasureSpec);
        }
    }
    

    part3:遍历设定每个子View需要摆放的位置,直接通过调用子View的layout方法,将具体的参数传入即可。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        //设置ViewGroup的高度
        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
        //这里让每个子View都显示完整的一屏
        mlp.height = mScreenHeight*childCount;
        setLayoutParams(mlp);
        for (int i = 0;i<childCount;i++){
            View child = getChildAt(i);
            if(child.getVisibility()!=GONE){
                //修改每个子View的top和bottom两个属性,让他们可以依次排列下来
                child.layout(l,i*mScreenHeight,r,(i+1)*mScreenHeight);
            }
        }
    }
    

    part4:在onTouchEvent中实现滚动的逻辑和"粘性"的逻辑。

     @Override
    public boolean onTouchEvent(MotionEvent event) {
        int y = (int)event.getY();
        switch(event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mLastY = y;
                //记录触摸起点
                mStart = getScrollY();
                break;
            case MotionEvent.ACTION_MOVE:
                if(!mScroller.isFinished()){
                    mScroller.abortAnimation();
                }
                int dy = mLastY - y;
                if(getScrollY()<0){
                    dy = 0;
                }
                if(getScrollY()>getHeight()-mScreenHeight){
                    dy = 0;
                }
                scrollBy(0,dy);
                mLastY = y;
                break;
            case MotionEvent.ACTION_UP:
                //记录触摸终点
                mEnd = getScrollY();
                int dScrollY = mEnd - mStart;
                if(dScrollY>0){
                    if(dScrollY<mScreenHeight/3){
                        mScroller.startScroll(0,getScrollY(),0,-dScrollY);
                    }else{
                        mScroller.startScroll(0,getScrollY(),0,mScreenHeight-dScrollY);
                    }
                }else{
                    if(-dScrollY<mScreenHeight/3){
                        mScroller.startScroll(0,getScrollY(),0,-dScrollY);
                    }else{
                        mScroller.startScroll(0,getScrollY(),0,-mScreenHeight-dScrollY);
                    }
                }
                break;
        }
        postInvalidate();
        return true;
    }
    @Override
    public void computeScroll() {
        super.computeScroll();
        if(mScroller.computeScrollOffset()){
            scrollTo(0,mScroller.getCurrY());
            postInvalidate();
        }
    }
    

    相关文章

      网友评论

      • 2d41962da5f2:请问案例三,part2中canvas.drawText(mShowText,0,mShowText.length(),mCircleXY,mCircleXY+(mShowTextSize/4),mTextPaint);
        },为什么要mCircleXY+mShowTextSize/4, 这个地方不太明白。谢谢你的分享
        JuliusL:那个参数只是确定Text的Y坐标,不必太纠结,至于为什么那样写,由于上面的代码是我抄书上的,我只能猜测原作者可能是因为那样写更好看一些,或者是那样写更对齐一些。
      • 学android的小新:安卓群英传

      本文标题:Android难点之——自定义View(中)

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