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

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

作者: 坑逼的严 | 来源:发表于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