美文网首页
自定义View,绘制

自定义View,绘制

作者: 编程_书恨少 | 来源:发表于2020-11-03 14:09 被阅读0次

1.使用绘制方法,画出一个view,然后在xml文件中使用即可

1.创建自定义的view
这里我只画了一个空心圆,canvas是画布,paint是画笔,用画笔可以画出任何图形设置颜色、空心实心、线条宽度,通过RectF设置圆的位置和大小

public class PaintView extends View {

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

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        RectF rectF = new RectF(10, 10, 100, 100);
        Paint paint = new Paint();

        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.RED);
        paint.setStrokeWidth(10);

        canvas.drawOval(rectF, paint);
    }
}

2.将画好的view引用到xml中

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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.mazhan.view.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.mazhan.view.PaintView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

这样我们绘制的view就可以被加载出来了


Snip20201022_18.png

2.绘制线条时的注意点
1.需要设置stroke
2.二阶的贝塞尔曲线可以设置跟随手势画出任意的图形,这个在线教育中使用的画板就是使用的二阶贝塞尔曲线

public class CanvasPathView extends View {
    public CanvasPathView(Context context) {
        super(context);
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth(10);
        // 如果需要绘制path的话,需要设置paint的style为stroke
        paint.setStyle(Paint.Style.STROKE);

        // 线条需要通过path来绘制
        Path path = new Path();
        // 设置起点
        path.moveTo(50,50);
        // 设置连接点
        path.lineTo(100,100);

        //二阶贝塞尔曲线
        path.quadTo(200,200,300,100);

        canvas.drawPath(path, paint);
    }
}
Snip20201022_20.png

3.移动坐标系

默认的坐标系是(0,0),可以通过设置canvas的属性来平移或者是旋转坐标系,然后将坐标系的模式保存下来,这样可以便利的设置一些特殊的坐标系

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.RED);
        paint.setStrokeWidth(10);

        // 设置坐标系起始点为100,100
        canvas.translate(100,100);

        // 保存设置好的坐标系
        canvas.save();

        // 设置坐标系旋转
        canvas.rotate(90);

        // 销毁坐标系,还原上一次保存的坐标系
        canvas.restore();

        canvas.drawLine(0,0,100,0,paint);
    }

4.自定义控件的三大方法执行顺序

public class MyFrameLayout extends FrameLayout {

    public MyFrameLayout(@NonNull Context context) {
        super(context);
    }

    public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Log.d(getClass().getSimpleName(), "onDraw: " + "MyFrameLayout父控件");
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        Log.d(getClass().getSimpleName(), "onMeasure: " +  "MyFrameLayout父控件");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        Log.d(getClass().getSimpleName(), "onLayout: " +  "MyFrameLayout父控件");
    }
}


public class MyView extends View {

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

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setTextSize(20);
        canvas.drawText("这是一个view", 10, 10, paint);

        Log.d(getClass().getSimpleName(), "onDraw: " + "MyView子控件");

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        Log.d(getClass().getSimpleName(), "onMeasure: " + "MyView子控件");

    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        Log.d(getClass().getSimpleName(), "onLayout: " + "MyView子控件");

    }
}

在xml中使用MyFrameLayout和MyView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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.mazhan.customviewmethodorder.MainActivity">

    <com.mazhan.customviewmethodorder.MyFrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <com.mazhan.customviewmethodorder.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    </com.mazhan.customviewmethodorder.MyFrameLayout>

</LinearLayout>
10-22 07:20:51.088 6735-6735/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
10-22 07:20:51.150 6735-6735/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
10-22 07:20:51.153 6735-6735/com.mazhan.customviewmethodorder D/MyFrameLayout: onLayout: MyFrameLayout父控件
10-22 07:28:38.713 6928-6928/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
                                                                              
10-22 07:28:38.753 6928-6928/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
10-22 07:28:38.760 6928-6928/com.mazhan.customviewmethodorder D/MyFrameLayout: onLayout: MyFrameLayout父控件
10-22 07:30:32.941 7079-7079/com.mazhan.customviewmethodorder D/MyView: onMeasure: MyView子控件
10-22 07:30:32.943 7079-7079/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
                                                                               
10-22 07:30:32.995 7079-7079/com.mazhan.customviewmethodorder D/MyView: onMeasure: MyView子控件
10-22 07:30:32.995 7079-7079/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
10-22 07:30:32.996 7079-7079/com.mazhan.customviewmethodorder D/MyView: onLayout: MyView子控件
10-22 07:30:32.996 7079-7079/com.mazhan.customviewmethodorder D/MyFrameLayout: onLayout: MyFrameLayout父控件
10-22 07:30:33.188 7079-7079/com.mazhan.customviewmethodorder D/MyView: onDraw: MyView子控件

5.VierPager的使用

1.在MainActivity的xml中添加ViewPager

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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.mazhan.viewpagerdemo.MainActivity">

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPagerId"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_purple">


    </android.support.v4.view.ViewPager>


</LinearLayout>

2.给ViewPager设置PagerAdapter

public class MainActivity extends AppCompatActivity {

    private ArrayList<TextView> mDatas;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initData();
        initViews();
    }

    private void initViews() {
        ViewPager viewPager = (ViewPager) findViewById(R.id.viewPagerId);

        viewPager.setAdapter(new PagerAdapter() {
            @Override
            public int getCount() {
                return mDatas.size();
            }

            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view == object;
            }

            @Override
            public Object instantiateItem(ViewGroup container, int position) {

                TextView textView =  mDatas.get(position);
                container.addView(textView);
                return textView;
            }

            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {

                container.removeView((View) object);
            }
        });
    }

    private void initData() {
        mDatas = new ArrayList<>();
        for (int i = 0; i < 10; i++) {

            TextView textView = new TextView(getApplicationContext());
            textView.setTextSize(20);
            textView.setText("这是第" + i + "个页面");
            mDatas.add(textView);
        }
    }
}

6.ViewFliper的使用
使用viewFlipper完成文字向上滚动的效果(只要设置滚动的动画即可)

在xml中直接使用系统的ViewFlipper即可

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    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.mazhan.viewfliperdemo.MainActivity">

    <ViewFlipper
        android:id="@+id/ViewFlipperId"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/myColorRed">
    </ViewFlipper>

</android.support.constraint.ConstraintLayout>
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();

    }

    private void initView() {
        ViewFlipper viewFlipper = (ViewFlipper) findViewById(R.id.ViewFlipperId);
        viewFlipper.startFlipping();

        //设置viewFlipper的一些属性
        viewFlipper.setFlipInterval(5000);

        // 设置viewFlipper的的入场动画和离场动画
        // 组合动画   透明度  位移
        // 离场动画
        AnimationSet inAnimationSet = new AnimationSet(false);
        AnimationSet outAnimationSet = new AnimationSet(false);

        AlphaAnimation inAlphaAnimation = new AlphaAnimation(0, 1);
        TranslateAnimation inTranslateAnimation = new TranslateAnimation(
                TranslateAnimation.RELATIVE_TO_SELF, 0,
                TranslateAnimation.RELATIVE_TO_SELF, 0,
                TranslateAnimation.RELATIVE_TO_SELF, 1,
                TranslateAnimation.RELATIVE_TO_SELF, 0);
        inAnimationSet.addAnimation(inAlphaAnimation);
        inAnimationSet.addAnimation(inTranslateAnimation);
        inAnimationSet.setDuration(3000);


        AlphaAnimation outAlphaAnimation = new AlphaAnimation(1, 0);
        TranslateAnimation outTranslateAnimation = new TranslateAnimation(
                TranslateAnimation.RELATIVE_TO_SELF, 0,
                TranslateAnimation.RELATIVE_TO_SELF, 0,
                TranslateAnimation.RELATIVE_TO_SELF, 0,
                TranslateAnimation.RELATIVE_TO_SELF, -1);
        outAnimationSet.addAnimation(outAlphaAnimation);
        outAnimationSet.addAnimation(outTranslateAnimation);
        outAnimationSet.setDuration(2000);

        viewFlipper.setInAnimation(inAnimationSet);
        viewFlipper.setOutAnimation(outAnimationSet);

        //向ViewFlipper中添加要滚动的view
        for (int i = 0; i < 10; i++) {
            TextView textView = new TextView(getApplicationContext());
            textView.setText("这是第" + i + "个textView");
            viewFlipper.addView(textView);
        }
    }
}

7.自定义控件自定义属性

1.比如说自定义一个只滚动text的ViewFlipper,叫做textViewFlipper。如果要给这个控件添加自定义属性,那么需要新建一个文件


image.png

2.那么如何在xml中使用自定义的属性呢


image.png

3.这样在自定义的控件类被加载的时候,就会调用构造方法,那么就可以得到自定义的属性名和属性值


image.png

4.将自定义的属性设置给textView
这里自定义属性的获取方式并不是这样遍历的,是直接通过字段获取的,R.styleable,R.styleable.TextViewFlipper_fontColor,R.styleable.TextViewFlipper_fontSize,这些都是系统根据自定义属性的文件生成的。

注意点:这里获取属性的时机是要注意的,只能在构造方法里进行获取,只有在构造方法被调用的时候,attrs的值才是正确的,当构造方法执行完成之后,attrs的值会发生变化,也就得不到自定义的属性了。


public class TextViewFlipper extends ViewFlipper {

    private AttributeSet mAttrs;
    private Context mContext;
    private float mTextSize;
    private int mTextColor;

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

    public TextViewFlipper(Context context, AttributeSet attrs) {
        super(context, attrs);

        mContext = context;
        mAttrs = attrs;

        int attributeCount = attrs.getAttributeCount();
        for (int i = 0; i < attributeCount; i++) {
            String attributeName = attrs.getAttributeName(i);
            String attributeValue = attrs.getAttributeValue(i);

            Log.d(getClass().getSimpleName(), "TextFlipperView: attributeName: " + attributeName
                    +" attributeValue: "+attributeValue);
        }

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TextViewFlipper);
        mTextColor = typedArray.getColor(R.styleable.TextViewFlipper_fontColor, Color.BLACK);
        mTextSize = typedArray.getDimension(R.styleable.TextViewFlipper_fontSize, 100);
        //销毁typedArray 
        typedArray.recycle();
    }


    void setViews (ArrayList<TextView> textViews) {

        for (int i = 0; i < textViews.size(); i++) {
            TextView textView = textViews.get(i);
            textView.setTextColor(mTextColor);
            textView.setTextSize(mTextSize);
            addView(textView);
        }
    }

}

8.制作垂直滑动的滑块demo
自定义VerticalSlideView,里面放一个ImageView,让imageView跟随手指拖动而滑动


public class VerticalSlideView extends ViewGroup {

    private View mChildView;
    private float mStartY;
    private float mStartX;


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

    public VerticalSlideView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    // 确定控件的大小
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mChildView = getChildAt(0);

        //1. 获得mode和size
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int withSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // 测量自控件的宽度
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        // 2.判断mode,根据mode来设置size
        int childWidth = mChildView.getMeasuredWidth();

        // 设置自身控件的大小
        setMeasuredDimension(childWidth, heightSize - dip2px(10));
    }


    // 对子控件进行布局
    int mTop = 0;
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 位置:左上右下
        mChildView.layout(0, mTop, mChildView.getMeasuredWidth(), mTop+mChildView.getMeasuredHeight());
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        // 这里没有判断action的类型,是点击还是滑动还是抬起
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // do sth
                break;

            case MotionEvent.ACTION_MOVE:
                // do sth
                break;
            case MotionEvent.ACTION_UP:
                // do sth
                break;
            default:
                break;
        }

        // 直接设置滑块的y值跟随触摸的y值变化
        float y = event.getY();
        mTop = (int) y;
        requestLayout();

        if (y < 0) {
            mTop = 0;
        }

        if (y > getMeasuredHeight() - mChildView.getMeasuredHeight()) {
            mTop =  getMeasuredHeight() - mChildView.getMeasuredHeight();
        }

        requestLayout();
        return true;
    }



    public int px2dip(float pxValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    // 将do转化为像素尺寸
    public int dip2px( float dpValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}

在xml中使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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.mazhan.verticalslideview.MainActivity">

    <com.mazhan.verticalslideview.VerticalSlideView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="20dp"
        android:background="#f00">
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/tabicon"/>
    </com.mazhan.verticalslideview.VerticalSlideView>

</LinearLayout>

9.无限滚动的背景View
通过绘制背景图片来实现

1.创建自定义的属性,来设置图片滚动的参数,创建attrs属性文件,设置参数

  <?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ScrollingView" >
        <attr name="speed" format="integer"/>
        <attr name="src" format="reference"/>
    </declare-styleable>
</resources>

2.在xml中使用自定义的属性

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    xmlns:mazhan="http://schemas.android.com/apk/res-auto"
    tools:context="com.mazhan.scrollingview.MainActivity">


    <com.mazhan.scrollingview.ScrollingView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        mazhan:src="@drawable/scrolling_background"
        mazhan:speed="3"/>


</LinearLayout>

3.创建自定义的ScrollingView,在里面绘制bitmap,通过刷新bitmap的绘制来提现出动画效果


public class ScrollingView extends View {
    private int mSpeed;
    private Paint mPaint;
    private Bitmap mBitmap;

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

    public ScrollingView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        // 获取自定义的属性值
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScrollingView);

        mSpeed = typedArray.getInteger(R.styleable.ScrollingView_speed, 1);
        int resourceId = typedArray.getResourceId(R.styleable.ScrollingView_src, 0);
        mBitmap = BitmapFactory.decodeResource(getResources(), resourceId);
        typedArray.recycle();

        mPaint = new Paint();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSize =  MeasureSpec.getSize(widthMeasureSpec);
        setMeasuredDimension(widthSize, mBitmap.getHeight());
    }

    int mOffset = 0;
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //1 画图片

        //节约效率,避免控件在看不见的地方疯狂绘制,对偏移量需要控制
        //负数到了一定的范围,就重置,范围设置为bitmap的宽度
        if(mOffset<-mBitmap.getWidth()){
            mOffset = 0;
        }

        //2 填充控件剩下的宽度,多绘制几个bitmap上去
        int left = (int) mOffset;
        while (left<getMeasuredWidth()){
//            一直往右边去绘制,填充
            canvas.drawBitmap(mBitmap,left,0,mPaint);
            left+=mBitmap.getWidth();
        }

        mOffset -= mSpeed;
        //4 让控件重新绘制
        invalidate();
    }
}

相关文章

网友评论

      本文标题:自定义View,绘制

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