PullView.java
/**
* Created by jian on 17-8-16.
*/
public class PullView extends View {
//圓的畫筆
private Paint mCirClePaint;
//圓的半徑
private float MCirCleRadius = 50;
//可拖動的高度
private int mDragHeight = 300;
//進度值
private float mProgress;
private float mCirClePointX, mCirClePointY;
//目標寬度
private int mTargetWidth = 400;
//貝塞爾曲線的路勁以及畫筆
private Paint mPathPaint;
private Path mPath = new Path();
//重心點最總高度,決定控制點的Y座標
private int mTarGetGravityHeight = 10;
//角度變換,0-135度
private int mTangentAngle = 105;
private Drawable mContent = null;
private int mContentMargin= 0;
private Interpolator mProgressInterpolator = new DecelerateInterpolator();
private Interpolator mTangentInterpolator;
public PullView(Context context) {
super(context);
init(null);
}
public PullView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public PullView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public PullView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
/**
* 當進行測量的時候觸發
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//寬度的意圖。類型
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
//高度的類型
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int measureWidth;
int measureHeight;
int iWidth = (int) (2 * MCirCleRadius + getPaddingLeft() + getPaddingRight());
int iHeight = (int) ((mDragHeight * mProgress + 0.5f) + getPaddingTop() + getPaddingBottom());
if (widthMode == MeasureSpec.EXACTLY) {
//卻確的
measureWidth = width;
} else if (widthMode == MeasureSpec.AT_MOST) {
//最多的
measureWidth = Math.min(iWidth, width);
} else {
measureWidth = iWidth;
}
if (heightMode == MeasureSpec.EXACTLY) {
//卻確的
measureHeight = height;
} else if (heightMode == MeasureSpec.AT_MOST) {
//最多的
measureHeight = Math.min(iHeight, height);
} else {
measureHeight = iHeight;
}
setMeasuredDimension(measureWidth, measureHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//進行基礎座標參數系改變
int count = canvas.save();
float tranX = (getWidth() - getValueByLine(getWidth(), mTargetWidth, mProgress)) / 2;
canvas.translate(tranX, 0);
//畫貝塞爾曲線
canvas.drawPath(mPath, mPathPaint);
//畫圓
canvas.drawCircle(mCirClePointX, mCirClePointY, MCirCleRadius, mCirClePaint);
Drawable drawable = mContent;
if(drawable!=null){
//剪切舉行區域
Log.i("Tag","----=--lai");
canvas.save();
canvas.clipRect(drawable.getBounds());
//繪製Drawble
drawable.draw(canvas);
canvas.restore();
}
canvas.restoreToCount(count);
}
/**
* 當大小該表時調用
*
* @param w
* @param h
* @param oldw
* @param oldh
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//當高度變化時,進行更新
updatePathLayout();
}
/**
* 初始化方法
* @param attrs
*/
private void init(AttributeSet attrs) {
final Context context = getContext();
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.PullView);
int color = array.getColor(R.styleable.PullView_pColor,0xff000000);
MCirCleRadius = array.getDimension(R.styleable.PullView_pRadius,50);
mDragHeight = array.getDimensionPixelOffset(R.styleable.PullView_pDragHeight,mDragHeight);
mTangentAngle =array.getInteger(R.styleable.PullView_pTangentAngle,100);
mTargetWidth = array.getDimensionPixelOffset(R.styleable.PullView_pTargetWidth,mTargetWidth);
mTarGetGravityHeight = array.getDimensionPixelOffset(R.styleable.PullView_pTargetGravityHeight,mTarGetGravityHeight);
//銷燬
mContent = array.getDrawable(R.styleable.PullView_pContentDrawBle);
mContentMargin= array.getDimensionPixelOffset(R.styleable.PullView_pConTentDrawableMargin,0);
array.recycle();
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
//設置看鋸齒
p.setAntiAlias(true);
//設置防抖動
p.setDither(true);
//設置爲填充方式
p.setStyle(Paint.Style.FILL);
p.setColor(color);
mCirClePaint = p;
p = new Paint(Paint.ANTI_ALIAS_FLAG);
//設置看鋸齒
p.setAntiAlias(true);
//設置防抖動
p.setDither(true);
//設置爲填充方式
p.setStyle(Paint.Style.FILL);
p.setColor(color);
mPathPaint = p;
//切角路勁插值器
mTangentInterpolator = PathInterpolatorCompat.create((MCirCleRadius*2.0f)/mDragHeight,90.0f/mTangentAngle);
}
public void setProgress(float progress) {
Log.i("TAGPULLVIEW", progress + "");
this.mProgress = progress;
//請求重新繪製
requestLayout();
}
/**
* 更行我們的路徑
*/
private void updatePathLayout() {
//獲取進度
final float progress = mProgressInterpolator.getInterpolation(mProgress);
//獲取可繪製區域高度寬度
final float w = getValueByLine(getWidth(), mTargetWidth, mProgress);
final float h = getValueByLine(0, mDragHeight, mProgress);
//圓的中心點X座標
final float cPointX = w / 2;
//圓的半徑
final float cRadius = MCirCleRadius;
//圓的中心點Y座標
final float cPointY = h - cRadius;
//控制點借宿Y的值
final float endControlY = mTarGetGravityHeight;
mCirClePointX = cPointX;
mCirClePointY = cPointY;
final Path path = mPath;
//復位操作
path.reset();
path.moveTo(0, 0);
//左邊部分的結束點和控制點
float lEndPointX, lEndPointY;
float lControlPointX, lControlPointY;
//獲取當前切線的弧度
float angle =mTangentAngle* mTangentInterpolator.getInterpolation(progress);
double radian = Math.toRadians(angle);
float x = (float) (Math.sin(radian) * cRadius);
float y = (float) (Math.cos(radian) * cRadius);
lEndPointX = cPointX - x;
lEndPointY = cPointY + y;
//控制點Y的變化
lControlPointY = getValueByLine(0, endControlY, progress);
//控制點與結束點的高度
float tHeight = lEndPointY - lControlPointY;
//控制點與X的座標距離
float tWidth = (float) (tHeight / Math.tan(radian));
lControlPointX = lEndPointX - tWidth;
path.quadTo(lControlPointX, lControlPointY, lEndPointX, lEndPointY);
//鏈接到右邊
path.lineTo(cPointX + (cPointX - lEndPointX), lEndPointY);
path.quadTo(cPointX + cPointX - lControlPointX, lControlPointY, w, 0);
updateContentLayout(cPointX,cPointY,cRadius);
}
/**
* 對內容部分進行測量病設置
* @param cx
* @param cy
* @param radius
*/
private void updateContentLayout(float cx,float cy,float radius){
Drawable drawable = mContent;
if(drawable!= null)
{
int margin = mContentMargin;
int l = (int) (cx - radius+margin);
int r = (int) (cx + radius-margin);
int t = (int) (cy - radius+margin);
int b= (int) (cy + radius-margin);
drawable.setBounds(l,t,r,b);
}
}
/**
* 獲取當前值
*
* @param start 起始值
* @param end 結束值
* @param progress 進度
* @return
*/
private float getValueByLine(float start, float end, float progress) {
return start + (end - start) * progress;
}
//釋放動畫
private ValueAnimator valueAnimator;
/**
* 釋放操作動畫
*/
public void release() {
if (valueAnimator == null) {
ValueAnimator animator = ValueAnimator.ofFloat(mProgress, 0f);
animator.setInterpolator(new DecelerateInterpolator());//設置動畫減速
animator.setDuration(400);//動畫時間
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
Object val = animator.getAnimatedValue();
if (val instanceof Float) {
setProgress((Float) val);
}
}
});
valueAnimator = animator;
} else {
valueAnimator.cancel();
valueAnimator.setFloatValues(mProgress, 0f);
}
valueAnimator.start();
}
}
ic_draw_cricle.xml
attr_pull.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PullView">
<attr name="pColor" format="color" />
<attr name="pRadius" format="dimension" />
<attr name="pDragHeight" format="dimension" />
<attr name="pTangentAngle" format="integer" />
<attr name="pTargetWidth" format="dimension" />
<attr name="pTargetGravityHeight" format="dimension" />
<attr name="pContentDrawBle" format="reference" />
<attr name="pConTentDrawableMargin" format="dimension" />
</declare-styleable>
</resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.ying.myview.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="100dp">
<com.example.ying.myview.PullView
android:id="@+id/pullView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:pColor="@color/colorAccent"
app:pConTentDrawableMargin="2dp"
app:pContentDrawBle="@drawable/ic_draw_cricle"
app:pDragHeight="100dp"
app:pRadius="20dp"
app:pTangentAngle="110"
app:pTargetGravityHeight="4dp"
app:pTargetWidth="200dp"
/>
</LinearLayout>
</LinearLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
private static final float TOUCH_MOVE_MAX_Y = 600;
private float mTouchMoveStartX;
private float mTouchMoveStartY = 0;
private PullView pullView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pullView = (PullView) findViewById(R.id.pullView);
findViewById(R.id.activity_main).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int action = motionEvent.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mTouchMoveStartY = motionEvent.getY();
return true;
case MotionEvent.ACTION_MOVE:
float y = motionEvent.getY();
if (y > mTouchMoveStartY) {
float moveSize = y-mTouchMoveStartY;
float progress = moveSize>=TOUCH_MOVE_MAX_Y?1:moveSize/TOUCH_MOVE_MAX_Y;
pullView.setProgress(progress);
}
break;
case MotionEvent.ACTION_UP:
pullView.release();
break;
}
return false;
}
});
}
}