贝塞尔曲线也是自定义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();
}
}
其实贝塞尔曲线的作用很大,要多多熟悉他哦。
最后还是那句:不喜勿碰。
网友评论