未来会怎样,我不知道,我只是想为了比今天好
老规矩,看看效果
嗯,前面自定义了两个视图容器,今天这个是自定义View,开始自定义前,我们应该理清自己的思路,怎么来做这个东西.用我们的QQ,我们会发现,它可以拖动,有两个圆,中间像个橡皮泥连着,所以我是这么想的,我们可以在它初始状态下,画两个圆,一个固定不动,一个可以拖动,且上面还要画个数字.嗯,我们先来完成这个.
public class DragBall extends View {
private Paint mPaint;
private PointF mCicleCenterPoint;
private PointF mDragCicleCenter;
private float mRadius;
private float mDragRadius;
private Paint mTextPaint;
private int mStatusBarHeight;
private int mRang;
private float saveRdius = 0;
private int mNumber = 0;
public DragBall(Context context) {
this(context, null);
}
public DragBall(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragBall(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setAntiAlias(true); //抗锯齿
mPaint.setStyle(Paint.Style.FILL); //填充
mPaint.setDither(true); //防抖动
mRadius = dp2px(context, 9); //半径9dp
mDragRadius = mRadius;
saveRdius = mRadius;
mTextPaint = new Paint();
mTextPaint.setTextAlign(Paint.Align.CENTER); //字居中对齐
mTextPaint.setColor(Color.WHITE);
mTextPaint.setTextSize(mRadius * 1.2f);
mTextPaint.setAntiAlias(true);
mTextPaint.setDither(true);
mStatusBarHeight = getStatusBarHeight();
Log.d("DragBall", "mStatusBarHeight:" + mStatusBarHeight);
}
public void setCenterPoint(float x, float y) {
mCicleCenterPoint = new PointF(x, y);
mDragCicleCenter = new PointF(x, y);
}
public void setDragRang(int rang) { //拖动范围
mRang = rang;
}
public void setNumber(int number) {
mNumber = number;
}
public int getStatusBarHeight() {//获取状态栏高度
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
public int dp2px(Context context, int dp) {
return (int) (context.getResources().getDisplayMetrics().density * dp);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(mCicleCenterPoint.x, mCicleCenterPoint.y, mRadius, mPaint); //画固定圆
canvas.drawCircle(mDragCicleCenter.x, mDragCicleCenter.y, mDragRadius, mPaint);//画拖动圆
canvas.drawText("" + mNumber, mDragCicleCenter.x, mDragCicleCenter.y + mDragRadius / 2, mTextPaint); //画文字
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float delX;
float dely;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
mDragCicleCenter.x = event.getRawX(); //手机坐标
mDragCicleCenter.y = event.getRawY();
break;
case MotionEvent.ACTION_UP:
mDragCicleCenter.x = mCicleCenterPoint.x;
mDragCicleCenter.y = mCicleCenterPoint.y; //回到初始位置,固定圆不动
break;
}
invalidate(); //刷新界面 调onDraw
return true;
}
}
继承view都要我们重写onDraw方法,里面画板canvas可以画许多东西,这里就不细讲了,我也细讲不了.自我感觉还没理解到那个程度,但基本的我们还是会用的,我们通过触摸事件不断更新拖动圆的圆心坐标和不断重绘invalidate(),达到拖动的效果,但是,发现了吧,这感觉圆心不在我们点的那个位置啊,这是因为画板画的时候,算了状态栏高度,而我们的点是没有算状态栏高度的,即我们本来画(100,100)这个点,画板给我们画到了(100,100+状态栏高度),所以我们要把我们的画板上移状态栏高度再进行绘画.
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(0,-mStatusBarHeight);
canvas.drawCircle(mCicleCenterPoint.x, mCicleCenterPoint.y, mRadius, mPaint); //画固定圆
canvas.drawCircle(mDragCicleCenter.x, mDragCicleCenter.y, mDragRadius, mPaint);//画拖动圆
canvas.drawText("" + mNumber, mDragCicleCenter.x, mDragCicleCenter.y + mDragRadius / 2, mTextPaint); //画文字
canvas.restore();
}
这样我们的圆就指哪画哪了是吧,这里canvas.save()和canvas.restore(),即保存和恢复,为什么要这样呢,因为你想,onDraw执行了多少次,如果你不保存好状态,也不恢复的话,画板会移到哪里去..这也说明了,画是一层一层画上去的,不是只在一层上,每次画就画在对应的那个层上.不然如果在一个层的话,又恢复了,那不等于没移对吧.然后,画完这两个圆后,我们应该去画我们中间那个东西了,那个东西其实是画路径画出来的,我们来看个图加深下理解
画板canva有个方法drawPath,即画路径,而Path路径,可以走直线,也可以走贝塞尔曲线,什么是贝塞尔曲线呢,来看个图 这具体是什么算法我们不要管.,我们调用path.quadTo方法就好了,它前两个参数是p1控制点的横纵坐标,后两个参数是p2终点的横纵坐标,这时你会说,起点呢,起点就是path出发的那个点,我们new Path的时候,一般要执行个方法path.moveTo就是站到那个点去,如果不掉,默认最开始起点为(0,0),然后是lineTo(直走)还是quadTo(贝塞尔曲线走)随便你,每条路径的终点即为下一路径的起点.所以来到我画的那张图,其实中间那个橡皮泥一样的东西就是path先moveTo A 然后贝塞尔到C再直线到D,再贝塞尔到B,然后path关闭,path会形成回路,即它的终点,直线到它最开始的那个起点A点,这样ACDB连接起来就是中间那个东西了,我们来看下代码吧
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(0,-mStatusBarHeight);
Path path = new Path();
path.moveTo(mPointA.x, mPointA.y); //到A点
path.quadTo(mPointE.x, mPointE.y, mPointC.x, mPointC.y); //从A走贝塞尔曲线到C点
path.lineTo(mPointD.x, mPointD.y); //从C走直线到D点
path.quadTo(mPointE.x, mPointE.y, mPointB.x, mPointB.y); //从D点走贝塞尔曲线到B点
path.close(); //关闭路径,形成回路最后终点B点直线到起点A点
canvas.drawPath(path, mPaint); //画出路径
canvas.drawCircle(mCicleCenterPoint.x, mCicleCenterPoint.y, mRadius, mPaint); //画固定圆
canvas.drawCircle(mDragCicleCenter.x, mDragCicleCenter.y, mDragRadius, mPaint);//画拖动圆
canvas.drawText("" + mNumber, mDragCicleCenter.x, mDragCicleCenter.y + mDragRadius / 2, mTextPaint); //画文字
canvas.restore();
}
/**
* 计算图中的ABCDE点,在触摸move的时候调用
*/
private void ComputerFivePoint() {
mPointE.x = mDragCicleCenter.x / 2 - mCicleCenterPoint.x / 2 + mCicleCenterPoint.x;
mPointE.y = mDragCicleCenter.y / 2 - mCicleCenterPoint.y / 2 + mCicleCenterPoint.y;
double tan = (mDragCicleCenter.x - mCicleCenterPoint.x) / (mDragCicleCenter.y - mCicleCenterPoint.y);
double atan = Math.atan(tan); //得到角度
mPointA.x = (float) (mCicleCenterPoint.x + Math.cos(atan) * mRadius);
mPointA.y = (float) (mCicleCenterPoint.y - Math.sin(atan) * mRadius);
mPointB.x = (float) (mCicleCenterPoint.x - Math.cos(atan) * mRadius);
mPointB.y = (float) (mCicleCenterPoint.y + Math.sin(atan) * mRadius);
mPointC.x = (float) (mDragCicleCenter.x + Math.cos(atan) * mDragRadius);
mPointC.y = (float) (mDragCicleCenter.y - Math.sin(atan) * mDragRadius);
mPointD.x = (float) (mDragCicleCenter.x - Math.cos(atan) * mDragRadius);
mPointD.y = (float) (mDragCicleCenter.y + Math.sin(atan) * mDragRadius);
invalidate();
}
好了,做到这里就差不多了,接下来的就是设置范围,然后根据范围把固定圆的半径根据拉出距离和半径比缩小,再就是拖出范围,不画固定圆和中间那段路径,再就是抬起判断,如果在距离内,回到初始位置,如果一直都没出过范围就执行回弹动画,超出距离,消失,都不画.嗯,就是一堆判断考验细心耐心,还是有点注释,加上我前面讲的.还是自己看吧.
public class DragBall extends View {
private Paint mPaint;
private PointF mCicleCenterPoint;
private PointF mDragCicleCenter;
private float mRadius;
private float mDragRadius;
private Paint mTextPaint;
private int mStatusBarHeight;
private PointF mPointE;
private PointF mPointA;
private PointF mPointB;
private PointF mPointC;
private PointF mPointD;
private int mRang;
private boolean isOutOfRang = false;
private float saveRdius = 0;
private boolean isDisappear = false;
private int mNumber = 0;
public DragBall(Context context) {
this(context, null);
}
public DragBall(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragBall(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setAntiAlias(true); //抗锯齿
mPaint.setStyle(Paint.Style.FILL); //填充
mPaint.setDither(true); //防抖动
mRadius = dp2px(context,9);
mDragRadius = mRadius;
saveRdius = mRadius;
mTextPaint = new Paint();
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setColor(Color.WHITE);
mTextPaint.setTextSize(mRadius * 1.2f);
mTextPaint.setAntiAlias(true);
mTextPaint.setDither(true);
mStatusBarHeight = getStatusBarHeight();
Log.d("DragBall", "mStatusBarHeight:" + mStatusBarHeight);
}
public void setCenterPoint(float x, float y) {
mCicleCenterPoint = new PointF(x, y);
mDragCicleCenter = new PointF(x, y);
mPointA = new PointF(x + mRadius, y);
mPointB = new PointF(x - mRadius, y);
mPointC = new PointF(x + mRadius, y);
mPointD = new PointF(x - mRadius, y);
mPointE = new PointF(x, y);
}
public void setDragRang(int rang) { //拖动范围
mRang = rang;
}
public void setNumber(int number) {
mNumber = number;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(0, -mStatusBarHeight);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(mCicleCenterPoint.x, mCicleCenterPoint.y, mRang, mPaint);
mPaint.setStyle(Paint.Style.FILL);
if (!isOutOfRang) {
Path path = new Path();
path.moveTo(mPointA.x, mPointA.y); //到A点
path.quadTo(mPointE.x, mPointE.y, mPointC.x, mPointC.y); //从A走贝塞尔曲线到C点
path.lineTo(mPointD.x, mPointD.y); //从C走直线到D点
path.quadTo(mPointE.x, mPointE.y, mPointB.x, mPointB.y); //从D点走贝塞尔曲线到B点
path.close(); //关闭路径,形成回路最后终点B点直线到起点A点
canvas.drawPath(path, mPaint); //画出路径
canvas.drawCircle(mCicleCenterPoint.x, mCicleCenterPoint.y, mRadius, mPaint); //画固定圆
}
if (!isDisappear) {
canvas.drawCircle(mDragCicleCenter.x, mDragCicleCenter.y, mDragRadius, mPaint);//画拖动圆
canvas.drawText("" + mNumber, mDragCicleCenter.x, mDragCicleCenter.y + mDragRadius / 2, mTextPaint); //画文字
}
canvas.restore();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float delX;
float dely;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
mDragCicleCenter.x = event.getRawX(); //手机坐标
mDragCicleCenter.y = event.getRawY();
ComputerFivePoint(); //计算ABCD坐标
delX = Math.abs(mDragCicleCenter.x - mCicleCenterPoint.x);
dely = Math.abs(mDragCicleCenter.y - mCicleCenterPoint.y);
if (Math.sqrt(delX * delX + dely * dely) <= mRang) { //拖动距离
mRadius = (float) (saveRdius - Math.sqrt(delX * delX + dely * dely) / mRang * (mRadius / 7 * 6)); //固定圆半径缩小
} else {
isOutOfRang = true;
}
break;
case MotionEvent.ACTION_UP:
if (isOutOfRang) {
delX = Math.abs(mDragCicleCenter.x - mCicleCenterPoint.x);
dely = Math.abs(mDragCicleCenter.y - mCicleCenterPoint.y);
if (Math.sqrt(delX * delX + dely * dely) < mRang) {
isOutOfRang = false;
mDragCicleCenter.x = mCicleCenterPoint.x;
mDragCicleCenter.y = mCicleCenterPoint.y;
ComputerFivePoint();
} else {
isDisappear = true;
invalidate();
if (monDragBallListener != null) {
monDragBallListener.onDisappear(this, mDragCicleCenter.x, mDragCicleCenter.y); //消失的时候
}
}
} else {
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
animator.setInterpolator(new OvershootInterpolator(5.0f));
final PointF startPoint = new PointF(mDragCicleCenter.x,mDragCicleCenter.y);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction(); //百分比
mDragCicleCenter.x = startPoint.x + (mCicleCenterPoint.x - startPoint.x) * fraction;
mDragCicleCenter.y = startPoint.y + (mCicleCenterPoint.y - startPoint.y) * fraction;
Log.d("DragBall", "fraction:" + fraction);
ComputerFivePoint();
}
});
animator.setDuration(200);
animator.start();
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (monDragBallListener != null){
monDragBallListener.onReset();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
mRadius = saveRdius; //固定圆半径还原
break;
}
return true;
}
/**
* 计算图中的ABCDE点
*/
private void ComputerFivePoint() {
mPointE.x = mDragCicleCenter.x / 2 - mCicleCenterPoint.x / 2 + mCicleCenterPoint.x;
mPointE.y = mDragCicleCenter.y / 2 - mCicleCenterPoint.y / 2 + mCicleCenterPoint.y;
double tan = (mDragCicleCenter.x - mCicleCenterPoint.x) / (mDragCicleCenter.y - mCicleCenterPoint.y);
double atan = Math.atan(tan); //得到角度
mPointA.x = (float) (mCicleCenterPoint.x + Math.cos(atan) * mRadius);
mPointA.y = (float) (mCicleCenterPoint.y - Math.sin(atan) * mRadius);
mPointB.x = (float) (mCicleCenterPoint.x - Math.cos(atan) * mRadius);
mPointB.y = (float) (mCicleCenterPoint.y + Math.sin(atan) * mRadius);
mPointC.x = (float) (mDragCicleCenter.x + Math.cos(atan) * mDragRadius);
mPointC.y = (float) (mDragCicleCenter.y - Math.sin(atan) * mDragRadius);
mPointD.x = (float) (mDragCicleCenter.x - Math.cos(atan) * mDragRadius);
mPointD.y = (float) (mDragCicleCenter.y + Math.sin(atan) * mDragRadius);
invalidate();
}
public int getStatusBarHeight() {//获取状态栏高度
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
public interface onDragBallListener {
void onDisappear(DragBall dragBall, float x, float y);
void onReset();
}
private onDragBallListener monDragBallListener;
public void setonDragBallListener(onDragBallListener onDragBallListener) {
monDragBallListener = onDragBallListener;
}
public int dp2px(Context context,int dp){
return (int) (context.getResources().getDisplayMetrics().density * dp);
}
}
这里说一下怎么把它加到listView的item中,你会发现我们把它弄在布局文件是不好的,反正我没搞成功.其实QQ应该(我说的是应该)也是先在布局那布局一个和这个差不多的textView,然后当我们触摸它的时候,通过touch事件的ActionDown获得那个位置,接着再通过WindowManger添加我们这个view去那个位置,然后把textView隐藏掉,并把touch事件传递给我们的view处理,这样就能满屏幕拖动了.
public class MainActivity extends Activity {
private DragBall mDragBall;
private WindowManager mWindowManager;
private WindowManager.LayoutParams mParams;
public static Handler mHandler = new Handler();
private float mDensity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.item_list);
mDensity = getResources().getDisplayMetrics().density;
final TextView msg_point = (TextView) findViewById(R.id.msg_point);
mDragBall = new DragBall(this);
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
mParams = new WindowManager.LayoutParams();
mParams.format = PixelFormat.TRANSLUCENT; //很重要!!,这个属性一定要加,不加的话,除了你添加的View能看到,其它黑乎乎一片,使透明就是你想要的效果了
msg_point.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
msg_point.getParent().requestDisallowInterceptTouchEvent(true);
msg_point.setVisibility(View.INVISIBLE);
mDragBall.setCenterPoint(event.getRawX(), event.getRawY());
mDragBall.setDragRang(150);
mDragBall.setNumber(50);
mWindowManager.addView(mDragBall, mParams);
}
mDragBall.onTouchEvent(event);
return true;
}
});
mDragBall.setonDragBallListener(new DragBall.onDragBallListener() {
@Override
public void onDisappear(DragBall dragBall, float x, float y) {
if (mWindowManager != null && mDragBall.getParent() != null){
mWindowManager.removeView(mDragBall);
}
ImageView imageView = new ImageView(MainActivity.this);
imageView.setImageResource(R.drawable.frame_bao); //帧动画
AnimationDrawable mAnimDrawable = (AnimationDrawable) imageView
.getDrawable();
final BaozhaLayout baozhaLayout= new BaozhaLayout(MainActivity.this); //测量帧动画的自定义控件
baozhaLayout.setCenter((int) x, (int) (y - getStatusBarHeight()));
baozhaLayout.addView(imageView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT));
mWindowManager.addView(bubbleLayout, mParams);
mAnimDrawable.start();
// 播放结束后,移除帧动画
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mWindowManager.removeView(baozhaLayout);
}
}, 600);
}
@Override
public void onReset() {
if (mWindowManager != null && mDragBall.getParent() != null){
mWindowManager.removeView(mDragBall);
msg_point.setVisibility(View.VISIBLE);
}
}
}
);
}
public int getStatusBarHeight() {//获取状态栏高度
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}
我们只得到了消息小球消失时的横纵坐标,直接放帧动画的话,会填充整个屏幕的,所以来看看里面怎么写的
public class BaozhaLayout extends FrameLayout {
public BaozhaLayout(Context context) {
super(context);
}
private int mCenterX, mCenterY;
public void setCenter(int x, int y){
mCenterX = x;
mCenterY = y;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
View child = getChildAt(0);
// 设置View到指定位置
if (child != null && child.getVisibility() != GONE) {
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
child.layout((int)(mCenterX - width / 2.0f), (int)(mCenterY - height / 2.0f) //确定了帧动画的位置和宽高
, (int)(mCenterX + width / 2.0f), (int)(mCenterY + height / 2.0f));
}
}
其实挺简单的,对吧,怕不贴出来被打啊.经过了控件测量就不会填充屏幕了.嗯,差不多了,就这样吧,加上我说的,和那些注释应该好理解吧.有问题可以问我,有兴趣的可以下载下来看看,额,上传的代码资源还没有审核通过,通过后,我会把单独的这个和整合了侧拉菜单侧拉删除的下载链接发上来.
单独:http://download.youkuaiyun.com/detail/z8z87878/9559500
整合前面两个控件http://download.youkuaiyun.com/detail/z8z87878/9559501