androidui系列-自定义view手绘小黄人

SingleShu888 SingleShu888     2022-12-13     249

关键词:

总是想尝试各种自定义控件,来熟悉谷歌提供的一些自定绘图的方法,那就画一个小黄人吧。我在git上找到一个小换人的源码。它是按照比例计算的,有一定的公式,我觉得太麻烦了。就用自己的理解画了一个写死大小的小黄人。先给大家看看效果。
git上的小黄人是这样的。

我用代码画出来的是这样的。

其实有很多地方是不一样的,我会把两套代码都贴出来,有兴趣的朋友可以自己去研究研究。画完之后对Path和canvas有了很多新的认识。以后自定义什么复杂控件,这些都是扎实的基础。

那么在来整理一下,思路。从哪开始画,需要使用一些什么方法来进行绘制。

具体方法都有注释,想要研究的就要自己去花时间看代码了。
git上的代码。

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;



class MinionView extends View


    private static final int DEFAULT_SIZE = 200; //View默认大小
    private int mWidthUnspecified;
    private int mHeightUnspecified;

    private Paint mPaint;
    private float mBodyWidth;
    private float mBodyHeight;
    private static final float BODY_SCALE = 0.6f;//身体主干占整个view的比重
    private static final float BODY_WIDTH_HEIGHT_SCALE = 0.6f; //        身体的比例设定为 w:h = 3:5

    private float mStrokeWidth = 4;//描边宽度
    private float mOffset;//计算时,部分需要 考虑找边偏移
    private float mRadius;//身体上下半圆的半径
    private int mColorClothes = Color.rgb(32, 116, 160);//衣服的颜色
    private int mColorBody = Color.rgb(249, 217, 70);//衣服的颜色
    private int mColorStroke = Color.BLACK;
    private RectF mBodyRect;
    private float mHandsHeight;//计算出吊带的高度时,可以用来做手的高度
    private float mFootHeight;//脚的高度,用来画脚部阴影时用

    public MinionView(Context context) 
        super(context);
    

    public MinionView(Context context, AttributeSet attrs) 
        super(context, attrs);
    

    public MinionView(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
    

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public MinionView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) 
        super(context, attrs, defStyleAttr, defStyleRes);
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
    


    /**
     * @param origin
     * @param isWidth 是否在测量宽
     * @return
     */
    private int measure(int origin, boolean isWidth) 
        int result;
        int specMode = MeasureSpec.getMode(origin);
        int specSize = MeasureSpec.getSize(origin);
        switch (specMode) 
            case MeasureSpec.EXACTLY:
            case MeasureSpec.AT_MOST:
                result = specSize;
                if (isWidth) 
                    mWidthUnspecified = result;
                 else 
                    mHeightUnspecified = result;
                
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                if (isWidth) //宽或高未指定的情况下,可以由另一端推算出来 - -如果两边都没指定就用默认值
                    result = (int) (mHeightUnspecified * BODY_WIDTH_HEIGHT_SCALE);
                 else 
                    result = (int) (mWidthUnspecified / BODY_WIDTH_HEIGHT_SCALE);
                
                if (result == 0) 
                    result = DEFAULT_SIZE;
                

                break;
        

        return result;
    


    @Override
    protected void onDraw(Canvas canvas) 
        initParams();
        initPaint();
        drawFeetShadow(canvas);//脚下的阴影
        drawFeet(canvas);//脚
        drawHands(canvas);//手
        drawBody(canvas);//身体
        drawClothes(canvas);//衣服
        drawEyesMouth(canvas);//眼睛,嘴巴
        drawBodyStroke(canvas);//最后画身体的描边,可以摭住一些过渡的棱角
    


    private void initParams() 
        mBodyWidth = Math.min(getWidth(), getHeight() * BODY_WIDTH_HEIGHT_SCALE) * BODY_SCALE;
        mBodyHeight = Math.min(getWidth(), getHeight() * BODY_WIDTH_HEIGHT_SCALE) / BODY_WIDTH_HEIGHT_SCALE * BODY_SCALE;

        mStrokeWidth = Math.max(mBodyWidth / 50, mStrokeWidth);
        mOffset = mStrokeWidth / 2;

        mBodyRect = new RectF();
        mBodyRect.left = (getWidth() - mBodyWidth) / 2;
        mBodyRect.top = (getHeight() - mBodyHeight) / 2;
        mBodyRect.right = mBodyRect.left + mBodyWidth;
        mBodyRect.bottom = mBodyRect.top + mBodyHeight;

        mRadius = mBodyWidth / 2;
        mFootHeight = mRadius * 0.4333f;

        mHandsHeight =  (getHeight() + mBodyHeight) / 2   + mOffset - mRadius * 1.65f ;

    

    private void drawBody(Canvas canvas) 

        initPaint();
        mPaint.setColor(mColorBody);
        mPaint.setStyle(Paint.Style.FILL);

        canvas.drawRoundRect(mBodyRect, mRadius, mRadius, mPaint);

    

    private void drawBodyStroke(Canvas canvas) 
        initPaint();
        mPaint.setColor(mColorStroke);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawRoundRect(mBodyRect, mRadius, mRadius, mPaint);
    

    private void drawClothes(Canvas canvas) 
        initPaint();

        RectF rect = new RectF();

        rect.left = (getWidth() - mBodyWidth) / 2 + mOffset;
        rect.top = (getHeight() + mBodyHeight) / 2 - mRadius * 2 + mOffset;
        rect.right = rect.left + mBodyWidth - mOffset * 2;
        rect.bottom = rect.top + mRadius * 2 - mOffset * 2;

        mPaint.setColor(mColorClothes);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(mStrokeWidth);
        canvas.drawArc(rect, 0, 180, true, mPaint);

        int h = (int) (mRadius * 0.5);
        int w = (int) (mRadius * 0.3);

        rect.left += w;
        rect.top = rect.top + mRadius - h;
        rect.right -= w;
        rect.bottom = rect.top + h;

        canvas.drawRect(rect, mPaint);

        //画横线
        initPaint();
        mPaint.setColor(mColorStroke);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(mStrokeWidth);
        float[] pts = new float[20];//5条线

        pts[0] = rect.left - w;
        pts[1] = rect.top + h;
        pts[2] = pts[0] + w;
        pts[3] = pts[1];

        pts[4] = pts[2];
        pts[5] = pts[3] + mOffset;
        pts[6] = pts[4];
        pts[7] = pts[3] - h;

        pts[8] = pts[6] - mOffset;
        pts[9] = pts[7];
        pts[10] = pts[8] + (mRadius - w) * 2;
        pts[11] = pts[9];

        pts[12] = pts[10];
        pts[13] = pts[11] - mOffset;
        pts[14] = pts[12];
        pts[15] = pts[13] + h;

        pts[16] = pts[14] - mOffset;
        pts[17] = pts[15];
        pts[18] = pts[16] + w;
        pts[19] = pts[17];
        canvas.drawLines(pts, mPaint);

        //画左吊带
        initPaint();
        mPaint.setColor(mColorClothes);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.FILL);
        Path path = new Path();
        path.moveTo(rect.left - w - mOffset, mHandsHeight);
        path.lineTo(rect.left + h / 4, rect.top + h / 2);
        final float smallW = w / 2 * (float) Math.sin(Math.PI / 4);
        path.lineTo(rect.left + h / 4 + smallW, rect.top + h / 2 - smallW);
        final float smallW2 = w / (float) Math.sin(Math.PI / 4) / 2;
        path.lineTo(rect.left - w - mOffset, mHandsHeight - smallW2);

        canvas.drawPath(path, mPaint);
        initPaint();
        mPaint.setColor(mColorStroke);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawPath(path, mPaint);
        initPaint();
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawCircle(rect.left + h / 5, rect.top + h / 4, mStrokeWidth*0.7f, mPaint);

        //画右吊带

        initPaint();
        mPaint.setColor(mColorClothes);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.FILL);
        path.reset();
        path.moveTo(rect.left - w + 2 * mRadius - mOffset, mHandsHeight);
        path.lineTo(rect.right - h / 4, rect.top + h / 2);
        path.lineTo(rect.right - h / 4 - smallW, rect.top + h / 2 - smallW);
        path.lineTo(rect.left - w + 2 * mRadius - mOffset, mHandsHeight - smallW2);

        canvas.drawPath(path, mPaint);
        initPaint();
        mPaint.setColor(mColorStroke);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawPath(path, mPaint);
        initPaint();
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawCircle(rect.right - h / 5, rect.top + h / 4, mStrokeWidth*0.7f, mPaint);

        //中间口袋
        initPaint();
        mPaint.setColor(mColorStroke);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.STROKE);

        path.reset();
        float radiusBigPokect = w / 2.0f;
        path.moveTo(rect.left + 1.5f * w, rect.bottom - h / 4);
        path.lineTo(rect.right - 1.5f * w, rect.bottom - h / 4);
        path.lineTo(rect.right - 1.5f * w, rect.bottom + h / 4);
        path.addArc(rect.right - 1.5f * w - radiusBigPokect * 2, rect.bottom + h / 4 - radiusBigPokect,
                rect.right - 1.5f * w, rect.bottom + h / 4 + radiusBigPokect, 0, 90);
        path.lineTo(rect.left + 1.5f * w + radiusBigPokect, rect.bottom + h / 4 + radiusBigPokect);

        path.addArc(rect.left + 1.5f * w, rect.bottom + h / 4 - radiusBigPokect,
                rect.left + 1.5f * w + 2 * radiusBigPokect, rect.bottom + h / 4 + radiusBigPokect, 90, 90);
        path.lineTo(rect.left + 1.5f * w, rect.bottom - h / 4 - mOffset);
        canvas.drawPath(path, mPaint);

//        下边一竖,分开裤子
        canvas.drawLine(mBodyRect.left + mBodyWidth / 2, mBodyRect.bottom - h * 0.8f, mBodyRect.left + mBodyWidth / 2, mBodyRect.bottom, mPaint);
//      左边的小口袋
        float radiusSamllPokect = w * 1.2f;
        canvas.drawArc(mBodyRect.left - radiusSamllPokect, mBodyRect.bottom - mRadius - radiusSamllPokect,
                mBodyRect.left + radiusSamllPokect, mBodyRect.bottom - mRadius + radiusSamllPokect, 80, -60, false, mPaint);
//      右边小口袋
        canvas.drawArc(mBodyRect.right - radiusSamllPokect, mBodyRect.bottom - mRadius - radiusSamllPokect,
                mBodyRect.right + radiusSamllPokect, mBodyRect.bottom - mRadius + radiusSamllPokect, 100, 60, false, mPaint);
//        canvas.drawArc(left + w/5,);
    

    private void drawEyesMouth(Canvas canvas) 

        float eyesOffset = mRadius * 0.1f;//眼睛中心处于上半圆直径 往上的高度偏移
        mPaint.setStrokeWidth(mStrokeWidth * 5);
//        计算眼镜带弧行的半径 分两段,以便眼睛中间有隔开的效果
        float radiusGlassesRibbon = (float) (mRadius / Math.sin(Math.PI / 20));
        RectF rect = new RectF();
        rect.left = mBodyRect.left + mRadius - radiusGlassesRibbon;
        rect.top = mBodyRect.top + mRadius - (float) (mRadius / Math.tan(Math.PI / 20)) - radiusGlassesRibbon - eyesOffset;
        rect.right = rect.left + radiusGlassesRibbon * 2;
        rect.bottom = rect.top + radiusGlassesRibbon * 2;
        canvas.drawArc(rect, 81, 3, false, mPaint);
        canvas.drawArc(rect, 99, -3, false, mPaint);

//眼睛半径
        float radiusEyes = mRadius / 3;
        initPaint();
        mPaint.setColor(Color.WHITE);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.FILL);

        canvas.drawCircle(mBodyRect.left + mBodyWidth / 2 - radiusEyes - mOffset, mBodyRect.top + mRadius - eyesOffset, radiusEyes, mPaint);
        canvas.drawCircle(mBodyRect.left + mBodyWidth / 2 + radiusEyes + mOffset, mBodyRect.top + mRadius - eyesOffset, radiusEyes, mPaint);

        mPaint.setColor(mColorStroke);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(mBodyRect.left + mBodyWidth / 2 - radiusEyes - mOffset, mBodyRect.top + mRadius - eyesOffset, radiusEyes, mPaint);
        canvas.drawCircle(mBodyRect.left + mBodyWidth / 2 + radiusEyes + mOffset, mBodyRect.top + mRadius - eyesOffset, radiusEyes, mPaint);

        final float radiusEyeballBlack = radiusEyes / 3;
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(mBodyRect.left + mBodyWidth / 2 - radiusEyes - mOffset, mBodyRect.top + mRadius - eyesOffset, radiusEyeballBlack, mPaint);
        canvas.drawCircle(mBodyRect.left + mBodyWidth / 2 + radiusEyes + mOffset, mBodyRect.top + mRadius - eyesOffset, radiusEyeballBlack, mPaint);

        mPaint.setColor(Color.WHITE);
        final float radiusEyeballWhite = radiusEyeballBlack / 2;
        canvas.drawCircle(mBodyRect.left + mBodyWidth / 2 - radiusEyes + radiusEyeballWhite - mOffset * 2,
                mBodyRect.top + mRadius - radiusEyeballWhite + mOffset - eyesOffset,
                radiusEyeballWhite, mPaint);
        canvas.drawCircle(mBodyRect.left + mBodyWidth / 2 + radiusEyes + radiusEyeballWhite,
                mBodyRect.top + mRadius - radiusEyeballWhite + mOffset - eyesOffset,
                radiusEyeballWhite, mPaint);

//        画嘴巴,因为位置和眼睛有相对关系,所以写在一块
        mPaint.setColor(mColorStroke);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mStrokeWidth);
        float radiusMonth = mRadius;
        rect.left = mBodyRect.left;
        rect.top = mBodyRect.top - radiusMonth / 2.5f;
        rect.right = rect.left + radiusMonth * 2;
        rect.bottom = rect.top + radiusMonth * 2;
        canvas.drawArc(rect, 95, -20, false, mPaint);

    


    private void drawFeet(Canvas canvas) 
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setColor(mColorStroke);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);

        float radiusFoot = mRadius / 3 * 0.4f;
        float leftFootStartX = mBodyRect.left + mRadius - mOffset * 2;
        float leftFootStartY = mBodyRect.bottom - mOffset;
        float footWidthA = mRadius * 0.5f;//脚宽度大-到半圆结束
        float footWidthB = footWidthA / 3;//脚宽度-比较细的部分

        //      左脚
        Path path = new Path();
        path.moveTo(leftFootStartX, leftFootStartY);
        path.lineTo(leftFootStartX, leftFootStartY + mFootHeight);
        path.lineTo(leftFootStartX - footWidthA + radiusFoot, leftFootStartY + mFootHeight);

        RectF rectF = new RectF();
        rectF.left = leftFootStartX - footWidthA;
        rectF.top = leftFootStartY + mFootHeight - radiusFoot * 2;
        rectF.right = rectF.left + radiusFoot * 2;
        rectF.bottom = rectF.top + radiusFoot * 2;
        path.addArc(rectF, 90, 180);
        path.lineTo(rectF.left + radiusFoot + footWidthB, rectF.top);
        path.lineTo(rectF.left + radiusFoot + footWidthB, leftFootStartY);
        path.lineTo(leftFootStartX, leftFootStartY);
        canvas.drawPath(path, mPaint);

//      右脚
        float rightFootStartX = mBodyRect.left + mRadius + mOffset * 2;
        float rightFootStartY = leftFootStartY;
        path.reset();
        path.moveTo(rightFootStartX, rightFootStartY);
        path.lineTo(rightFootStartX, rightFootStartY + mFootHeight);
        path.lineTo(rightFootStartX + footWidthA - radiusFoot, rightFootStartY + mFootHeight);

        rectF.left = rightFootStartX + footWidthA - radiusFoot * 2;
        rectF.top = rightFootStartY + mFootHeight - radiusFoot * 2;
        rectF.right = rectF.left + radiusFoot * 2;
        rectF.bottom = rectF.top + radiusFoot * 2;
        path.addArc(rectF, 90, -180);
        path.lineTo(rectF.right - radiusFoot - footWidthB, rectF.top);
        path.lineTo(rectF.right - radiusFoot - footWidthB, rightFootStartY);
        path.lineTo(rightFootStartX, rightFootStartY);
        canvas.drawPath(path, mPaint);


    

    private void drawFeetShadow(Canvas canvas) 

        mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
        canvas.drawOval(mBodyRect.left + mBodyWidth * 0.15f, mBodyRect.bottom - mOffset + mFootHeight,
                mBodyRect.right - mBodyWidth * 0.15f, mBodyRect.bottom - mOffset + mFootHeight + mStrokeWidth * 1.3f, mPaint);
    


    private void drawHands(Canvas canvas) 
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setColor(mColorBody);

//        左手
        Path path = new Path();
        float hypotenuse = mBodyRect.bottom - mRadius - mHandsHeight;
        float radiusHand = hypotenuse / 6;
        mPaint.setPathEffect(new CornerPathEffect(radiusHand));

        path.moveTo(mBodyRect.left, mHandsHeight);
        path.lineTo(mBodyRect.left - hypotenuse / 2, mHandsHeight + hypotenuse / 2);
        path.lineTo(mBodyRect.left + mOffset, mBodyRect.bottom - mRadius + mOffset);
        path.lineTo(mBodyRect.left  , mHandsHeight);//增加兼容性,path没闭合在一起机子上会使手的下面的点没办法与裤子重合
        canvas.drawPath(path, mPaint);

        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(mColorStroke);
        canvas.drawPath(path, mPaint);


//        右手
        path.reset();
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mColorBody);

        path.moveTo(mBodyRect.right, mHandsHeight);
        path.lineTo(mBodyRect.right + hypotenuse / 2, mHandsHeight + hypotenuse / 2);
        path.lineTo(mBodyRect.right  - mOffset, mBodyRect.bottom - mRadius + mOffset);
        path.lineTo(mBodyRect.right, mHandsHeight);
        canvas.drawPath(path, mPaint);

        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(mColorStroke);
        canvas.drawPath(path, mPaint);

//        一个慢动作  - -||| 拐点内侧
        path.reset();
        mPaint.setStyle(Paint.Style.FILL);
        path.moveTo(mBodyRect.left, mHandsHeight + hypotenuse / 2 - mStrokeWidth);
        path.lineTo(mBodyRect.left - mStrokeWidth * 2, mHandsHeight + hypotenuse / 2 + mStrokeWidth * 2);
        path.lineTo(mBodyRect.left, mHandsHeight + hypotenuse / 2 + mStrokeWidth);
        path.lineTo(mBodyRect.left, mHandsHeight + hypotenuse / 2 - mStrokeWidth);
        canvas.drawPath(path, mPaint);

        path.reset();
        path.moveTo(mBodyRect.right, mHandsHeight + hypotenuse / 2 - mStrokeWidth);
        path.lineTo(mBodyRect.right + mStrokeWidth * 2, mHandsHeight + hypotenuse / 2 + mStrokeWidth * 2);
        path.lineTo(mBodyRect.right, mHandsHeight + hypotenuse / 2 + mStrokeWidth);
        path.lineTo(mBodyRect.right, mHandsHeight + hypotenuse / 2 - mStrokeWidth);
        canvas.drawPath(path, mPaint);

    


    private void initPaint() 
        if (mPaint == null) 
            mPaint = new Paint();
         else 
            mPaint.reset();
        
        mPaint.setAntiAlias(true);//边缘无锯齿
    

    /*public void randomBodyColor() 
        Random random = new Random();
        mColorBody = Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
        invalidate();
    */

那么在看看我的代码与他的代码的区别。

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by ShuWen on 2017/6/27.
 */

public class MinionViewNew extends View 

    private static final int DEFAULT_SIZE = 200; //View默认大小
    private int mWidthUnspecified;
    private int mHeightUnspecified;

    private Paint mPaint;
    private float mBodyWidth;
    private float mBodyHeight;
    private static final float BODY_SCALE = 0.6f;//身体主干占整个view的比重
    private static final float BODY_WIDTH_HEIGHT_SCALE = 0.6f; //        身体的比例设定为 w:h = 3:5

    private float mStrokeWidth = 10;//描边宽度
    private float mOffset;//计算时,部分需要 考虑找边偏移
    private float mRadius;//身体上下半圆的半径
    private int mColorClothes = Color.rgb(32, 116, 160);//衣服的颜色
    private int mColorBody = Color.rgb(249, 217, 70);//衣服的颜色
    private int mColorStroke = Color.BLACK;
    private RectF mBodyRect;
    private float mHandsHeight;//计算出吊带的高度时,可以用来做手的高度
    private float mFootHeight;//脚的高度,用来画脚部阴影时用

    public MinionViewNew(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
    

    public MinionViewNew(Context context, AttributeSet attrs) 
        super(context, attrs);
    

    public MinionViewNew(Context context) 
        super(context);
    

    @Override
    protected void onDraw(Canvas canvas) 
        initParams();
        initPaint();

        drawFeetShadow(canvas);
        drawFeet(canvas);//脚
        drawBody(canvas);//身体
        drawEyesMouth(canvas);//眼睛,嘴巴
        drawHands(canvas);//手
        drawClothes(canvas);//衣服
    

    private void drawClothes(Canvas canvas) 
        initPaint();

        //肚子颜色
        mPaint.setColor(mColorClothes);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(mStrokeWidth);

        RectF rectF = new RectF();
        rectF.left = getWidth()/2 - 246;  //x
        rectF.top = getHeight()/2 - 110; //y
        rectF.right = rectF.left + 492;
        rectF.bottom = rectF.top + 505;

        canvas.drawArc(rectF,0,180,true,mPaint);

        Path path = new Path();
        path.moveTo(getWidth()/2-250,getHeight()/2+144);
        path.lineTo(getWidth()/2-150,getHeight()/2+144);
        path.lineTo(getWidth()/2-150,getHeight()/2+50);
        path.lineTo(getWidth()/2+150,getHeight()/2+50);
        path.lineTo(getWidth()/2+150,getHeight()/2+144);
        path.lineTo(getWidth()/2+250,getHeight()/2+144);

        mPaint.setColor(mColorClothes);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawPath(path,mPaint);

        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(8);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawPath(path,mPaint);

        //左吊带
        path.moveTo(getWidth()/2 - 250,getHeight()/2 - 50);
        path.lineTo(getWidth()/2 - 110,getHeight()/2 + 70);
        path.lineTo(getWidth()/2 - 130,getHeight()/2 + 100);
        path.lineTo(getWidth()/2 - 250,getHeight()/2 - 10);
        mPaint.setColor(mColorClothes);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawPath(path,mPaint);

        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawPath(path,mPaint);

        //右吊带
        path.moveTo(getWidth()/2 + 250,getHeight()/2 - 50);
        path.lineTo(getWidth()/2 + 110,getHeight()/2 + 70);
        path.lineTo(getWidth()/2 + 130,getHeight()/2 + 100);
        path.lineTo(getWidth()/2 + 250,getHeight()/2 - 10);
        mPaint.setColor(mColorClothes);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawPath(path,mPaint);

        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);

自定义view进阶--手绘地图

一:最近学习了自定义view,刚好就接到了相关的需求,于是就上手做了,下面描述一下需求    需求:简单的来说就是做一个地图,不同的是,为了追求美观,于是地图是一张由UI出的图片,p... 查看详情

自定义view系列一自定义view的构造函数,自定义属性(代码片段)

.../details/51147893;本文出自:【梁大盛的博客】自定义View系列一自定义View的构造函数,自定义属性引:自定义View对于Android开发者是一道坎.虽然说是坎但是也得走过去的!此系列文章作为学习自定义View的一系列学习笔记.在进入学习... 查看详情

探究drawable图片的加载原理和缩放规律

自定义View系列教程00–推翻自己和过往,重学自定义View自定义View系列教程01–常用工具介绍自定义View系列教程02–onMeasure源码详尽分析自定义View系列教程03–onLayout源码详尽分析自定义View系列教程04–Draw源码分析及其实践自定... 查看详情

自定义view基础-最易懂的自定义view原理系列

前言自定义View原理是Android开发者必须了解的基础;在了解自定义View之前,你需要有一定的知识储备;本文将全面解析关于自定义View中的所有知识基础。目录1.View的分类视图View主要分为两类:类别解释特点单一视图即一个View,... 查看详情

攻克android软键盘的疑难杂症

系列教程:推翻自己和过往,重学自定义View自定义View系列教程01–常用工具介绍自定义View系列教程02–onMeasure源码详尽分析自定义View系列教程03–onLayout源码详尽分析自定义View系列教程04–Draw源码分析及其实践自定义View系列教... 查看详情

androidui系列-view实现圆形进度条

我就不从canvas和paint开始说了,onMeasure,onLayout,onDraw这些方法的介绍和源码解析。网上一搜很多。一篇解释不清楚,多看几篇。话不多说了,先看看效果吧。做成gif显示会有问题。我们先来分析一下需要什么数据、1、... 查看详情

自定义view方法速查

...三版),便于速查。日后设计相关部分将进行补充。基于AndroidUI组件的实现原理,开发可以开发出定制的组件。自定义UI组件时,首先定义一个继承View基类的子类,然后重写View类的一个或多个方法。通常可以被用户重写的方法... 查看详情

android自定义view系列实战篇-view(代码片段)

QRatingViewAcustomviewforratingwhicheasytomakeanduse,butfunctionisexcellent.github-QRatingViewEffectPictureProperties<declare-styleablename="QRatingView"><!--未选中图片--><attrna 查看详情

android自定义view系列-measurespec(代码片段)

在【Android】自定义View系列-绘制流程一文中,在测量过程中,是通过一定的规则得出最后我们测量的宽高,然后通过setMeasuredDimension()保存结果。那么这里说到的规则主要就是---MeasureSpec和LayoutParamsMeasureSpec是?ÿ... 查看详情

android自定义view系列实战篇-viewgroup(代码片段)

自定义ViewGroup其实也不复杂,但是要对子View的margin属性支持,就还需要花点精力。下面自己写了一个自定义的FlowLayout,支持了本身的padding属性的同时,也支持子View的margin属性。基本注释都已尽可能详尽地写在代... 查看详情

android自定义view系列-view(代码片段)

QRatingViewAcustomviewforratingwhicheasytomakeanduse,butfunctionisexcellent.github-QRatingViewEffectPictureProperties<declare-styleablename="QRatingView"><!--未选中图片--><attrname="normalIcon"format="reference"/><!--选中图片--><... 查看详情

android自定义view系列-measurespec(代码片段)

在【Android】自定义View系列-绘制流程一文中,在测量过程中,是通过一定的规则得出最后我们测量的宽高,然后通过setMeasuredDimension()保存结果。那么这里说到的规则主要就是---MeasureSpec和LayoutParamsMeasureSpec是?ÿ... 查看详情

android自定义view系列-measurespec(代码片段)

在【Android】自定义View系列-绘制流程一文中,在测量过程中,是通过一定的规则得出最后我们测量的宽高,然后通过setMeasuredDimension()保存结果。那么这里说到的规则主要就是---MeasureSpec和LayoutParamsMeasureSpec是?ÿ... 查看详情

android自定义view系列实战篇-view(代码片段)

QRatingViewAcustomviewforratingwhicheasytomakeanduse,butfunctionisexcellent.github-QRatingViewEffectPictureProperties<declare-styleablename="QRatingView"><!--未选中图片--><attrname="normalIcon"format="reference"/><!--选中图片--><... 查看详情

自定义view

  这里是自定义view(二),上一篇关于自定义view的一些基本知识,比如说自定义view的步骤、会涉及到哪些函数以及如何实现自定义属性,同时实现了一个很基础的自定义控件,一个自定义的计时器,需要看的人可以点击这个... 查看详情

自定义ui属性动画(代码片段)

系列文章目录自定义UI基础知识自定义UI绘制饼图自定义UI圆形头像自定义UI自制表盘自定义UI简易图文混排自定义UI使用Camera做三维变换自定义UI属性动画文章目录系列文章目录前言属性动画和视图动画的区别android.view.View#animate使... 查看详情

androidui系列-viewgroup流式布局(代码片段)

很多时候,我们会遇见各种各样的需求,流式布局算是非常常见的一种。像各种菜单啊,展示之类的。其实这个很简单,可以自己手写一个,顺便练练自定义控件。先看看效果。那么先来分析一下,满足这... 查看详情

android--每日一问:如何实现自定义view?(代码片段)

经典回答回忆一下,你去面试时常被问到的自定义View方面的问题是那些。有没有:invalidate和postInvalidate方法的区别?自定义View的绘制流程?View的Touch事件分发流程?因为在实际的工作中并不是每个人都会涉及... 查看详情