昨天我寫了二階貝塞爾曲線之波浪圖之後,結果裝逼失敗。理由是大佬說這個波浪圖的效果很生硬,一般情況下波浪圖是配合手勢使用的。因此決定改寫該控件,實現利用手勢實現波浪圖。先來一張效果圖:
實現思路如下:
- 在RecyclerView的onTouch事件中,獲取當前頁面加載的最後一個可見item的下標Position,確認RecyclerView是否加載到底部?
- 上述確認加載到到底部後,處理onTouch事件(判斷是長按事件還是點擊事件)
- 在長按事件中將滑動過程中的X值傳給自定義控件,然後自定義控件根據x值繪製貝塞爾曲線並顯示控件;
- 在手勢擡起後調用自定義控件方法,重置參數並隱藏控件
一、我本來打算在RecyclerView的item佈局文件中自定義控件,但是發現獲取不到控件,後來想了一下,這樣會把最後一項Item佈局撐大,於是還是加在了根佈局上。
RecyclerView獲取最後一個可見Item有兩個方法,但是都需要實現同一個抽象類RecyclerView.OnScrollListener,然後我們可以使用一個boolean的標誌位來記錄RecyclerView是否加載到底部?代碼如下:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
//當滾動結束
if(newState == RecyclerView.SCROLL_STATE_IDLE){
//當前分頁加載的demo數據最多隻有41條,所以下標值是40
check =((LinearLayoutManager)mRecyclerView.getLayoutManager()).findLastVisibleItemPosition() == 40;
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//得到當前顯示的最後一個item的view
View lastChildView = recyclerView.getLayoutManager().getChildAt(recyclerView.getLayoutManager().getChildCount()-1);
//當前分頁加載的demo數據最多隻有41條,所以下標值是40
check = recyclerView.getLayoutManager().getPosition(lastChildView) == 40;
}
});
二、在onTouch事件判斷當前事件爲觸摸還是還是點擊、長按等其它事件,其實這個方法一般就兩種:
- 通過觸摸的時間來判斷
- 通過滑動的距離來判斷
由於爲了不影響長按事件,這裏選擇通過滑動距離來判斷,代碼如下:
@Override
public boolean onTouch(View v, MotionEvent event) {
if(check){
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
float moveY = event.getY();
if (Math.abs(moveX - downX) > dp5 && Math.abs(moveY - downY) > dp5) {
waveView.setPoint(moveX);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
float upX = event.getX();
float upY = event.getY();
//加載到頂部後的點擊或者長按事件
if (Math.abs(upX - downX) < dp5 && Math.abs(upY - downY) < dp5) {
return false;
}
break;
}
return true;
}
return false;
}
三、在長按事件中將滑動過程中的X值傳給自定義控件,然後自定義控件根據x值繪製貝塞爾曲線並顯示控件。整個過程就是自定義控件有一個公共方法接收參數,並刷新視圖。雖然自定義控件的核心還是繪製貝塞爾曲線,但是一些參數有了變化,這裏做一個說明。
製圖技術實在不行,因此將就看吧,看不懂就看源碼:
/**
* Created by 魏興 on 2017/6/12.
*/
public class WaveView extends View {
private static final String TAG = "WaveView";
//波浪畫筆
private Paint mPaint;
//波浪Path類
private Path mPath;
private float controlpoint;
//波紋的中間軸
private int mCenterY;
//屏幕高度
private int mScreenHeight;
//屏幕寬度
private int mScreenWidth;
public WaveView(Context context) {
super(context);
init();
}
public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public WaveView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPath = new Path();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.LTGRAY);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mScreenHeight = h;
mScreenWidth = w;
mCenterY = mScreenHeight * 3/ 5;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
int start;
int end;
boolean isLeft = true;
if(controlpoint>=mScreenWidth/2){
isLeft = false;
}
start = isLeft ? mCenterY/3:mCenterY;
end = isLeft ? mCenterY:mCenterY/3;
mPath.moveTo(0, start);
mPath.quadTo(controlpoint,mCenterY/3, mScreenWidth,end);
//填充矩形
mPath.lineTo(mScreenWidth, mScreenHeight);
mPath.lineTo(0, mScreenHeight);
mPath.close();
canvas.drawPath(mPath, mPaint);
}
public void setPoint(float x ){
this.controlpoint = x;
setVisibility(VISIBLE);
invalidate();
}
public void rest(){
this.controlpoint = 0;
setVisibility(GONE);
}
}
感覺手勢擡起以後貝塞爾曲線的圖形直接隱藏還是很生硬,加一個動畫:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
int startY;
int endY;
boolean isLeft = true;
if(controlpoint>=mScreenWidth/2){
isLeft = false;
}
startY = isLeft ? mCenterY/3:mCenterY;
endY = isLeft ? mCenterY:mCenterY/3;
mPath.moveTo(0, startY+offset);
mPath.quadTo(controlpoint,0+offset, mScreenWidth,endY+offset);
Log.e(TAG, "onDraw: "+(startY)+" " +0+" "+endY);
//填充矩形
mPath.lineTo(mScreenWidth, mScreenHeight);
mPath.lineTo(0, mScreenHeight);
mPath.close();
canvas.drawPath(mPath, mPaint);
}
public void setPoint(float x ){
if(!isRestting){
this.controlpoint = x;
setVisibility(VISIBLE);
invalidate();
}
}
private boolean isRestting = false;
public void rest(){
// setVisibility(GONE);
// controlpoint = 0;
isRestting = true;
ValueAnimator animator = ValueAnimator.ofInt(0, mScreenHeight);
animator.setDuration(400);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
offset = (int) animation.getAnimatedValue();
postInvalidate();
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
setVisibility(GONE);
controlpoint = 0;
mScreenHeight = height;
mCenterY = mScreenHeight * 3/ 5;
offset = 0;
isRestting = false;
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
}
再看看效果: