美文网首页自定义控件
贝塞尔曲线做画布和不规则布局

贝塞尔曲线做画布和不规则布局

作者: 坑逼的严 | 来源:发表于2019-02-27 11:21 被阅读76次

    贝塞尔曲线也是自定义View的常用知识点,在Android中也有API支持,二阶贝塞尔用的是quadTo,三阶是cubicTo,只用嘴巴说是没啥意思的,我们这来个小案例~画布。
    画布就是让人在屏幕上手指一动从而画下他们划过的痕迹是吧,好的我们下面开始。先直接上代码,CanvasLayout

    public class CanvasLayout extends View {
    
        private Paint mPaint;
        private Path mPath;
        private float mPreX;
        private float mPreY;
        private Context mContext;
    
        public CanvasLayout(Context context) {
            this(context, null);
        }
    
        public CanvasLayout(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public CanvasLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mContext = context;
            mPath = new Path();
            mPaint = new Paint();
            mPaint.setColor(Color.RED);
            mPaint.setStrokeWidth(getResources().getDimension(R.dimen.dp_3));
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setAntiAlias(true);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mPreX = event.getX();
                    mPreY = event.getY();
                    mPath.moveTo(mPreX, mPreY);
                    return true;
    
                case MotionEvent.ACTION_MOVE:
                    float mEndX = (mPreX + event.getX()) / 2;
                    float mEndY = (mPreY + event.getY()) / 2;
                    mPath.quadTo(mPreX,mPreY,mEndX,mEndY);
                    mPreX = event.getX();
                    mPreY = event.getY();
                    invalidate();
                    break;
                default:
                    break;
            }
            return super.onTouchEvent(event);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawPath(mPath,mPaint);
        }
    }
    

    在CanvasLayout构造方法中,我们只是简单定义Path和Paint的样式,重写onTouchEvent方法。当接收到ACTION_DOWN事件的时候利用mPath.moveTo(mPreX, mPreY);将起点放置在手指按下的位置。记得要返回true,不然下面的ACTION_MOVE将不会接收到消息,然后再ACTION_MOVE中,我们利用quadTo设置控制点和终点,这里可能大家不明白,控制点为啥是上一个点的X,Y坐标,终点是两者相加的一半?下面我们上个图:


    image.png

    这样控制点为两者的一半,就一目了然了,但是这样会有A到a0,a1到C的线段没有画出来,但是这不是问题,他的距离也就几像素,可以忽略不计的。整体效果如下:


    1551235120304(1).gif
    但是项目中我们会碰到这样的界面:
    1551236072494.gif

    变形状的布局是一个地图,红色背景我们可以做其他的,我在项目就遇到了,前面是高德地图的MapView,后面要打开我们的AR相机,也就是一个AR导航项目,这个怎么做呢?我就直接上代码了,下面再介绍。
    准备BesselLayout,做上面的可变形的layout,

    public class BesselLayout extends RelativeLayout {
        private Paint paint;
        private int screeWidthOrHeight;
        private int heigth;
        private int width;
        private int mapHeight=150;
        private int beiserHeight=0;
        private RelativeLayout mRe;
    
        public BesselLayout(Context context) {
            this(context,null);
        }
    
        public BesselLayout(Context context, AttributeSet attrs) {
            this(context, attrs,0);
        }
    
        public BesselLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            paint = new Paint();
            paint.setAntiAlias(true);
            setLayerType(View.LAYER_TYPE_SOFTWARE, null);
            View view = inflate(context, R.layout.map_layout, this);
            mRe = view.findViewById(R.id.rl);
            beiserHeight = 150;
        }
    
        public RelativeLayout getmRe() {
            return mRe;
        }
    
        @Override
        protected void dispatchDraw(Canvas canvas) {
            super.dispatchDraw(canvas);
            canvas.save();
            Path path=new Path();
            path.moveTo(0,0);
            path.lineTo(width,0);
            path.lineTo(width,mapHeight);
            path.quadTo(width/2,-beiserHeight,0,mapHeight);
            path.close();
            canvas.drawPath(path,paint);
    
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
            Path path2=new Path();
            path2.moveTo(0,0);
            path2.lineTo(width,0);
            path2.lineTo(width,mapHeight);
            path2.quadTo(width/2,-beiserHeight,0,mapHeight);
            path2.close();
            canvas.drawPath(path2,paint);
    
            canvas.restore();
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            getScreeWidth();
        }
    
        public void kaiguan(float start, float end){
            ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end);
            valueAnimator.setInterpolator(new LinearInterpolator());
            valueAnimator.setDuration(300);
            valueAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    super.onAnimationStart(animation);
                    beiserHeight=0;
                }
    
            });
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    float mAnimatedValue = (float) valueAnimator.getAnimatedValue();
                    beiserHeight= (int) (mAnimatedValue*150);
                    mapHeight=(int)(mAnimatedValue*150);
                    if(mapHeight<=0){
                        mapHeight=0;
                    }
                    if(beiserHeight<=0){
                        beiserHeight = 0;
                    }
                    invalidate();
                }
            });
            valueAnimator.start();
        }
    
        public void getScreeWidth(){
            //2、通过Resources获取
            DisplayMetrics dm = getResources().getDisplayMetrics();
            heigth = dm.heightPixels;
            width = dm.widthPixels;
            screeWidthOrHeight = heigth > width ? width : heigth;
        }
    }
    

    在构造方法里面设置Paint,和关闭GPU加速,然后添加个布局进来通过

    View view = inflate(context, R.layout.map_layout, this);
    

    dispatchDraw里面我们画两个不规则矩形,就是采用贝塞尔曲线,画的一个底部向上凹陷的矩形,画两个一模一样,中间用paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));隔开,这样这个不规则矩形区域就是透明的了,如图:


    image.png

    绿色区域就是变透明的区域,粉红色框框就是原本矩形的大小,但是我们的底部用的是贝塞尔曲线画的,当控制点在上方的时候就会向上凹陷了。
    至于怎么让这个变直线或变曲线以及动画,就只要控制贝塞尔的控制点了,但是控制点的高度最低也是A、B点的的高度,不然曲线会变成向下的了。

    然后再Activity的布局里面使用

    <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"
        android:background="#ff3355"
        tools:context=".property.Main30Activity">
        <com.easy.customeasytablayout.customviews.property.BesselLayout
            android:id="@+id/my_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="@dimen/dp_300">
    
        </com.easy.customeasytablayout.customviews.property.BesselLayout>
        <Button
            android:id="@+id/m_btn_kaiguan"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:text="开关"/>
    </RelativeLayout>
    
    public class Main30Activity extends AppCompatActivity {
    
        private BesselLayout myLayout;
        private RelativeLayout.LayoutParams myLayoutLayoutParams;
        private int mTopMargin;
        private boolean mOpen = false;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main30);
            myLayout = findViewById(R.id.my_layout);
            findViewById(R.id.m_btn_kaiguan).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if(mOpen){
                        mOpen = false;
                        kaiguan(0,1);
                        myLayout.kaiguan(-1,1);
                    }else{
                        mOpen = true;
                        kaiguan(1,0);
                        myLayout.kaiguan(1,-1);
                    }
    
                }
            });
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            myLayoutLayoutParams = (RelativeLayout.LayoutParams) myLayout.getLayoutParams();
            mTopMargin = myLayoutLayoutParams.topMargin;
        }
    
        public void kaiguan(final float start, final float end) {
            ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end);
            valueAnimator.setInterpolator(new LinearInterpolator());
            valueAnimator.setDuration(300);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    float mAnimatedValue = (float) valueAnimator.getAnimatedValue();
                    myLayoutLayoutParams = (RelativeLayout.LayoutParams) myLayout.getLayoutParams();
                    int topMargin = (int) (mTopMargin*mAnimatedValue);
                    myLayoutLayoutParams.setMargins(0, topMargin, 0, 0);
                    myLayout.setLayoutParams(myLayoutLayoutParams);
                }
            });
            valueAnimator.start();
        }
    }
    

    其实贝塞尔曲线的作用很大,要多多熟悉他哦。
    最后还是那句:不喜勿碰。

    相关文章

      网友评论

        本文标题:贝塞尔曲线做画布和不规则布局

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