貝塞爾曲線也是自定義View的常用知識點(diǎn),在Android中也有API支持停撞,二階貝塞爾用的是quadTo瓷蛙,三階是cubicTo,只用嘴巴說是沒啥意思的戈毒,我們這來個(gè)小案例~畫布艰猬。
畫布就是讓人在屏幕上手指一動從而畫下他們劃過的痕跡是吧,好的我們下面開始埋市。先直接上代碼冠桃,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構(gòu)造方法中,我們只是簡單定義Path和Paint的樣式道宅,重寫onTouchEvent方法食听。當(dāng)接收到ACTION_DOWN事件的時(shí)候利用mPath.moveTo(mPreX, mPreY);將起點(diǎn)放置在手指按下的位置。記得要返回true污茵,不然下面的ACTION_MOVE將不會接收到消息樱报,然后再ACTION_MOVE中,我們利用quadTo設(shè)置控制點(diǎn)和終點(diǎn)泞当,這里可能大家不明白迹蛤,控制點(diǎn)為啥是上一個(gè)點(diǎn)的X,Y坐標(biāo),終點(diǎn)是兩者相加的一半襟士?下面我們上個(gè)圖:
這樣控制點(diǎn)為兩者的一半盗飒,就一目了然了,但是這樣會有A到a0陋桂,a1到C的線段沒有畫出來逆趣,但是這不是問題,他的距離也就幾像素章喉,可以忽略不計(jì)的汗贫。整體效果如下:
但是項(xiàng)目中我們會碰到這樣的界面:
變形狀的布局是一個(gè)地圖身坐,紅色背景我們可以做其他的,我在項(xiàng)目就遇到了落包,前面是高德地圖的MapView部蛇,后面要打開我們的AR相機(jī),也就是一個(gè)AR導(dǎo)航項(xiàng)目咐蝇,這個(gè)怎么做呢涯鲁?我就直接上代碼了,下面再介紹有序。
準(zhǔn)備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;
}
}
在構(gòu)造方法里面設(shè)置Paint警绩,和關(guān)閉GPU加速,然后添加個(gè)布局進(jìn)來通過
View view = inflate(context, R.layout.map_layout, this);
dispatchDraw里面我們畫兩個(gè)不規(guī)則矩形盅称,就是采用貝塞爾曲線肩祥,畫的一個(gè)底部向上凹陷的矩形,畫兩個(gè)一模一樣缩膝,中間用paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));隔開混狠,這樣這個(gè)不規(guī)則矩形區(qū)域就是透明的了,如圖:
綠色區(qū)域就是變透明的區(qū)域疾层,粉紅色框框就是原本矩形的大小将饺,但是我們的底部用的是貝塞爾曲線畫的,當(dāng)控制點(diǎn)在上方的時(shí)候就會向上凹陷了痛黎。
至于怎么讓這個(gè)變直線或變曲線以及動畫予弧,就只要控制貝塞爾的控制點(diǎn)了,但是控制點(diǎn)的高度最低也是A舅逸、B點(diǎn)的的高度桌肴,不然曲線會變成向下的了。
然后再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="開關(guān)"/>
</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();
}
}
其實(shí)貝塞爾曲線的作用很大琉历,要多多熟悉他哦坠七。
最后還是那句:不喜勿碰。