技术标签: # UI Path进阶 气泡拖拽爆炸 QQ拖拽气泡
先上效果图
先将这个过程分状态(阶段):
1.静止状态,小球的默认状态,由红色的气泡和中间的文本组成;
2.连接状态(拖拽半径短),拖拽过程中由随着手指一定的气泡和中间不断缩小的小气泡组成;
3.分离状态(拖拽半径长),中间的小气泡消失,连接的中间线也消失;
4.回弹或隐藏状态,当从连接状态松手,移动的气泡会回弹到中心,从分离状态松手,移动的气泡爆炸,整个页面隐藏。
自定义一个DragBubbleView继承View,静止、连接和分离状态都是需要绘制一个大的可动气泡,只是坐标不同,连接状态时,首先随着手指的移动,改变可动气泡的坐标,同时在中心绘制固定气泡,固定气泡的圆心随拖拽半径的变大二变小,大气泡和小气泡中间连接处是两条二阶贝赛尔曲线,贝塞尔曲线可以参考上一小节总结 Android Path进阶之Path常用API,第一条二阶贝塞尔曲线的两个数据点分别是A和B,第二条二阶贝塞尔曲线的两个数据点分别是C和D,两条贝塞尔曲线的控制点都是大气泡和小气泡的圆心连线的中点G,然后两条贝塞尔曲线和两条直线BC和DA闭和,外加大圆和小圆的部分,3部分刚好形成连接状态时的图形绘制,分离状态只需要绘制大气泡,爆炸时使用5张连续的图片顺序播放形成爆炸效果。
对过程进行了分解之后开始实现,首先确定需要使用到的变量,并会其初始化。一只画笔绘制可动气泡中心文本,一只画笔绘制两个气泡和中心不规则图形,一只画笔绘制爆炸时效果,一个路径(Path)用来记录两个气泡中间连接不规则的图形,然后在构造方法中对画笔和路径进行初始化,在onSizeChange中对坐标点初始化。
public class DragBubbleView extends View {
private static final int BUBBLE_STATE_DEFAULT = 0;//气泡默认状态--静止
private static final int BUBBLE_STATE_CONNECT = 1;//气泡相连
private final int BUBBLE_STATE_APART = 2;//气泡分离
private final int BUBBLE_STATE_DISMISS = 3;//气泡消失
private float mBubbleRadius = 60;//气泡半径
private int mBubbleColor = Color.RED;//气泡颜色
private String mTextStr = "99+";//气泡消息文字
private int mTextColor = Color.WHITE;//气泡消息文字颜色
private float mTextSize = 60;//气泡消息文字大小
private float mBubFixedRadius = mBubbleRadius;//不动气泡的半径
private float mBubMovableRadius = mBubbleRadius;//可动气泡的半径
private PointF mBubFixedCenter;//不动气泡的圆心
private PointF mBubMovableCenter;//可动气泡的圆心
private Paint mBubblePaint;//气泡的画笔
private Path mBezierPath;//贝塞尔曲线path
private Paint mTextPaint;//文本绘制画笔
private Rect mTextRect;//文本绘制区域
private Paint mBurstPaint;//气泡爆炸消失画笔
private Rect mBurstRect;//爆炸绘制区域
private int mBubbleState = BUBBLE_STATE_DEFAULT;//气泡状态标志
private float mDist;//两气泡圆心距离
private float mMaxDist = 4 * mBubbleRadius;//气泡相连状态最大圆心距离
private float MOVE_OFFSET = 2 * mBubbleRadius;//手指触摸偏移量
private Bitmap[] mBurstBitmapsArray;//气泡爆炸的bitmap数组
private boolean mIsBurstAnimStart = false;//是否在执行气泡爆炸动画
private int mCurDrawableIndex;//当前气泡爆炸图片index
//气泡爆炸的图片id数组
private int[] mBurstDrawablesArray = {
R.drawable.burst_1,
R.drawable.burst_2,
R.drawable.burst_3,
R.drawable.burst_4,
R.drawable.burst_5
};
public DragBubbleView(Context context) {
this(context, null);
}
public DragBubbleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DragBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//初始化气泡画笔
mBubblePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBubblePaint.setColor(mBubbleColor);
mBubblePaint.setStyle(Paint.Style.FILL);
//初始化文本绘制画笔
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
mTextRect = new Rect();
//初始化爆炸画笔
mBurstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBurstPaint.setFilterBitmap(true);
//初始化贝赛尔曲线path
mBezierPath = new Path();
mBurstRect = new Rect();
//将气泡爆炸的drawable转为bitmap
mBurstBitmapsArray = new Bitmap[mBurstDrawablesArray.length];
for (int i = 0; i < mBurstDrawablesArray.length; i++) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mBurstDrawablesArray[i]);
mBurstBitmapsArray[i] = bitmap;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//不动气泡圆心
if (mBubFixedCenter == null) {
mBubFixedCenter = new PointF(w / 2, h / 2);
} else {
mBubFixedCenter.set(w / 2, h / 2);
}
//可动气泡圆心
if (mBubMovableCenter == null) {
mBubMovableCenter = new PointF(w / 2, h / 2);
} else {
mBubMovableCenter.set(w / 2, h / 2);
}
}
}
气泡的各种状态的改变都是通过手指在屏幕的状态决定的,因此需要对触摸事件进行监听。
1.DOWN事件。气泡的默认状态是静止状态,在Down事件时判断下是不是手指的坐标是否在大气泡的半径内,如果在,则将气泡状态改为连接状态,如果不是则不作处理,即保持原来的默认静止状态
2.MOVE事件。Move事件先判断如果气泡是隐藏状态则不作处理,如果不是,求出固定气泡圆心和移动坐标之间的距离,如果该距离小于我们定的临界值,则认为气泡还处于连接状态,如果该距离大于我们定的临界值,则将气泡的状态改为分离状态,同时设置移动气泡的坐标为移动的坐标,并改变固定气泡的半径,最后别忘了调用invalidate方法进行重绘。
3.UP事件。UP事件时我们只需要判断下气泡当前的状态。如果当前气泡是连接状态,我们需要将移动气泡重置到中心,并将气泡状态设置为默认状态。如果当前气泡的状态为断开状态,我们只需要执行爆炸动画,并将气泡状态设置为默认状态。气泡重置动画和爆炸动画后面讲。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mBubbleState != BUBBLE_STATE_DISMISS) {
mDist = (float) Math.hypot(event.getX() - mBubFixedCenter.x, event.getY() - mBubFixedCenter.y);
Log.d(TAG, "mDist: " + (mDist < mBubbleRadius + MOVE_OFFSET));
if (mDist < mBubbleRadius + MOVE_OFFSET) {
mBubbleState = BUBBLE_STATE_CONNECT;
} else {
mBubbleState = BUBBLE_STATE_DEFAULT;
}
}
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "mBubbleState: " + mBubbleState);
if (mBubbleState != BUBBLE_STATE_DISMISS && mBubbleState != BUBBLE_STATE_DEFAULT) {
mDist = (float) Math.hypot(event.getX() - mBubFixedCenter.x, event.getY() - mBubFixedCenter.y);
Log.d(TAG, "mDist: " + mDist);
mBubMovableCenter.set(event.getX(), event.getY());//设置移动气泡的坐标
mBubFixedRadius = mBubbleRadius - mDist / 8;//缩小固定气泡的半径
if (mDist < mMaxDist + MOVE_OFFSET) {
mBubbleState = BUBBLE_STATE_CONNECT;
} else {
mBubbleState = BUBBLE_STATE_APART;//将气泡变为断开状态
}
}
invalidate();
break;
case MotionEvent.ACTION_UP:
if (mBubbleState == BUBBLE_STATE_CONNECT) {
startBubbleResetAnim();//回弹效果
} else if (mBubbleState == BUBBLE_STATE_APART) {
startBubbleBurstAnim();//爆炸动画
}
break;
}
return true;
}
绘制是在ondraw方法中进行的,上面已经对气泡的每种状态进行了设置。接下来就是根据不同的状态绘制不同的状态。
1.默认静止状态、连接状态和分离状态大气泡,因为这三种状态都有一个大气泡,因此可以放一起绘制,因此只要不是隐藏状态,都需要绘制。
//一个气泡加消息数据
if (mBubbleState != BUBBLE_STATE_DISMISS) {
canvas.drawCircle(mBubMovableCenter.x, mBubMovableCenter.y, mBubMovableRadius, mBubblePaint);
mTextPaint.getTextBounds(mTextStr, 0, mTextStr.length(), mTextRect);
canvas.drawText(mTextStr, mBubMovableCenter.x - mTextRect.width() / 2, mBubMovableCenter.y + mTextRect.height() / 2, mTextPaint);
}
2.连接状态小圆和不规则图形的绘制。连接状态时移动气泡已经在上面绘制了,还剩下小圆的绘制和不规则图形的绘制,小圆的绘制简单,直接绘制。不规则图形的绘制主要是求控制点的坐标和A、B、C、D四点的坐标,然后利用Path形成闭和路径,然后绘制Path。
if (mBubbleState == BUBBLE_STATE_CONNECT) {
//绘制不动圆
canvas.drawCircle(mBubFixedCenter.x, mBubFixedCenter.y, mBubFixedRadius, mBubblePaint);
//绘制贝塞尔曲线
//控制点坐标
float ctrlX = (mBubFixedCenter.x + mBubMovableCenter.x) / 2;
float ctrlY = (mBubFixedCenter.y + mBubMovableCenter.y) / 2;
float sinTheta = (mBubMovableCenter.y - mBubFixedCenter.y) / mDist;
float cosTheta = (mBubMovableCenter.x - mBubFixedCenter.x) / mDist;
//B
float iBubMovableStartX = mBubMovableCenter.x + sinTheta * mBubMovableRadius;
float iBubMovableStartY = mBubMovableCenter.y - cosTheta * mBubMovableRadius;
//A
float iBubFixedEndX = mBubFixedCenter.x + mBubFixedRadius * sinTheta;
float iBubFixedEndY = mBubFixedCenter.y - mBubFixedRadius * cosTheta;
//D
float iBubFixedStartX = mBubFixedCenter.x - mBubFixedRadius * sinTheta;
float iBubFixedStartY = mBubFixedCenter.y + mBubFixedRadius * cosTheta;
//C
float iBubMovableEndX = mBubMovableCenter.x - mBubMovableRadius * sinTheta;
float iBubMovableEndY = mBubMovableCenter.y + mBubMovableRadius * cosTheta;
mBezierPath.reset();
mBezierPath.moveTo(iBubFixedStartX, iBubFixedStartY);
mBezierPath.quadTo(ctrlX, ctrlY, iBubMovableEndX, iBubMovableEndY);//二阶贝塞尔曲线
mBezierPath.lineTo(iBubMovableStartX, iBubMovableStartY);//一阶贝赛尔曲线
mBezierPath.quadTo(ctrlX, ctrlY, iBubFixedEndX, iBubFixedEndY);//二阶贝塞尔曲线
mBezierPath.close();//闭和曲线
canvas.drawPath(mBezierPath, mBubblePaint);//绘制两圆中间的区域
}
3.重置回弹动画。UP事件后可能会执行重置回弹动画或者爆炸效果,先来说说回弹动画,回弹动画,实际上是移动气泡的坐标从UP之后的坐标回到固定气泡的过程,因此可以采用属性动画实现,并且在动画结束前会有回弹效果,因此我们对属性动画设置差值器设置为OvershootInterpolator,该方法在UP事件时被调用。
private void startBubbleResetAnim() {
ValueAnimator animator = ValueAnimator.ofObject(new PointFEvaluator(), mBubMovableCenter, mBubFixedCenter);
animator.setDuration(300);
animator.setInterpolator(new OvershootInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBubMovableCenter = (PointF) animation.getAnimatedValue();
invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBubbleState = BUBBLE_STATE_DEFAULT;
}
});
animator.start();
}
4.爆炸效果。在断开状态松手,会实现一个爆炸效果,爆炸效果实际上是5张静图的顺序播放,这里也采用属性动画实现,动画的变化值为int,大小从0变化到爆炸数组的length。
private void startBubbleBurstAnim() {
mBubbleState = BUBBLE_STATE_DISMISS;
ValueAnimator animator = ValueAnimator.ofInt(0, mBurstBitmapsArray.length);
animator.setDuration(1000);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurDrawableIndex = (int) animation.getAnimatedValue();
invalidate();
}
});
animator.start();
}
值变了之后还要重绘,因此ondraw方法中也需要做相应的处理
if (mBubbleState == BUBBLE_STATE_DISMISS && mCurDrawableIndex < mBurstBitmapsArray.length) {
mBurstRect.set((int) (mBubMovableCenter.x - mBubMovableRadius),
(int) (mBubMovableCenter.y - mBubMovableRadius),
(int) (mBubMovableCenter.x + mBubMovableRadius),
(int) (mBubMovableCenter.y + mBubMovableRadius)
);
canvas.drawBitmap(mBurstBitmapsArray[mCurDrawableIndex], null, mBurstRect, mBurstPaint);
}
好了,讲完了,核心其实在连接状态的绘制,完整代码:
public class DragBubbleView extends View {
private static final int BUBBLE_STATE_DEFAULT = 0;//气泡默认状态--静止
private static final int BUBBLE_STATE_CONNECT = 1;//气泡相连
private static final String TAG = "DragBubbleView";
private final int BUBBLE_STATE_APART = 2;//气泡分离
private final int BUBBLE_STATE_DISMISS = 3;//气泡消失
private float mBubbleRadius = 60;//气泡半径
private int mBubbleColor = Color.RED;//气泡颜色
private String mTextStr = "99+";//气泡消息文字
private int mTextColor = Color.WHITE;//气泡消息文字颜色
private float mTextSize = 60;//气泡消息文字大小
private float mBubFixedRadius = mBubbleRadius;//不动气泡的半径
private float mBubMovableRadius = mBubbleRadius;//可动气泡的半径
private PointF mBubFixedCenter;//不动气泡的圆心
private PointF mBubMovableCenter;//可动气泡的圆心
private Paint mBubblePaint;//气泡的画笔
private Path mBezierPath;//贝塞尔曲线path
private Paint mTextPaint;//文本绘制画笔
private Rect mTextRect;//文本绘制区域
private Paint mBurstPaint;//气泡爆炸消失画笔
private Rect mBurstRect;//爆炸绘制区域
private int mBubbleState = BUBBLE_STATE_DEFAULT;//气泡状态标志
private float mDist;//两气泡圆心距离
private float mMaxDist = 4 * mBubbleRadius;//气泡相连状态最大圆心距离
private float MOVE_OFFSET = 2 * mBubbleRadius;//手指触摸偏移量
private Bitmap[] mBurstBitmapsArray;//气泡爆炸的bitmap数组
private boolean mIsBurstAnimStart = false;//是否在执行气泡爆炸动画
private int mCurDrawableIndex;//当前气泡爆炸图片index
//气泡爆炸的图片id数组
private int[] mBurstDrawablesArray = {
R.drawable.burst_1,
R.drawable.burst_2,
R.drawable.burst_3,
R.drawable.burst_4,
R.drawable.burst_5
};
public DragBubbleView(Context context) {
this(context, null);
}
public DragBubbleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DragBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//初始化气泡画笔
mBubblePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBubblePaint.setColor(mBubbleColor);
mBubblePaint.setStyle(Paint.Style.FILL);
//初始化文本绘制画笔
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
mTextRect = new Rect();
//初始化爆炸画笔
mBurstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBurstPaint.setFilterBitmap(true);
//初始化贝赛尔曲线path
mBezierPath = new Path();
mBurstRect = new Rect();
//将气泡爆炸的drawable转为bitmap
mBurstBitmapsArray = new Bitmap[mBurstDrawablesArray.length];
for (int i = 0; i < mBurstDrawablesArray.length; i++) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mBurstDrawablesArray[i]);
mBurstBitmapsArray[i] = bitmap;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//不动气泡圆心
if (mBubFixedCenter == null) {
mBubFixedCenter = new PointF(w / 2, h / 2);
} else {
mBubFixedCenter.set(w / 2, h / 2);
}
//可动气泡圆心
if (mBubMovableCenter == null) {
mBubMovableCenter = new PointF(w / 2, h / 2);
} else {
mBubMovableCenter.set(w / 2, h / 2);
}
}
@Override
protected void onDraw(Canvas canvas) {
//1,静止状态,一个气泡加消息数据
//2, 连接状态,一个气泡加消息数据,贝塞尔曲线,本身位置上气泡,大小可变化
//3,分离状态,一个气泡加消息数据
//4,消失状态,爆炸效果
Log.d(TAG, "onDraw mBubbleState: " + mBubbleState);
if (mBubbleState == BUBBLE_STATE_CONNECT) {
//绘制不动圆
canvas.drawCircle(mBubFixedCenter.x, mBubFixedCenter.y, mBubFixedRadius, mBubblePaint);
//绘制贝塞尔曲线
//控制点坐标
float ctrlX = (mBubFixedCenter.x + mBubMovableCenter.x) / 2;
float ctrlY = (mBubFixedCenter.y + mBubMovableCenter.y) / 2;
float sinTheta = (mBubMovableCenter.y - mBubFixedCenter.y) / mDist;
float cosTheta = (mBubMovableCenter.x - mBubFixedCenter.x) / mDist;
//B
float iBubMovableStartX = mBubMovableCenter.x + sinTheta * mBubMovableRadius;
float iBubMovableStartY = mBubMovableCenter.y - cosTheta * mBubMovableRadius;
//A
float iBubFixedEndX = mBubFixedCenter.x + mBubFixedRadius * sinTheta;
float iBubFixedEndY = mBubFixedCenter.y - mBubFixedRadius * cosTheta;
//D
float iBubFixedStartX = mBubFixedCenter.x - mBubFixedRadius * sinTheta;
float iBubFixedStartY = mBubFixedCenter.y + mBubFixedRadius * cosTheta;
//C
float iBubMovableEndX = mBubMovableCenter.x - mBubMovableRadius * sinTheta;
float iBubMovableEndY = mBubMovableCenter.y + mBubMovableRadius * cosTheta;
mBezierPath.reset();
mBezierPath.moveTo(iBubFixedStartX, iBubFixedStartY);
mBezierPath.quadTo(ctrlX, ctrlY, iBubMovableEndX, iBubMovableEndY);//二阶贝塞尔曲线
mBezierPath.lineTo(iBubMovableStartX, iBubMovableStartY);//一阶贝赛尔曲线
mBezierPath.quadTo(ctrlX, ctrlY, iBubFixedEndX, iBubFixedEndY);//二阶贝塞尔曲线
mBezierPath.close();//闭和曲线
canvas.drawPath(mBezierPath, mBubblePaint);//绘制两圆中间的区域
}
//一个气泡加消息数据
if (mBubbleState != BUBBLE_STATE_DISMISS) {
canvas.drawCircle(mBubMovableCenter.x, mBubMovableCenter.y, mBubMovableRadius, mBubblePaint);
mTextPaint.getTextBounds(mTextStr, 0, mTextStr.length(), mTextRect);
canvas.drawText(mTextStr, mBubMovableCenter.x - mTextRect.width() / 2, mBubMovableCenter.y + mTextRect.height() / 2, mTextPaint);
}
//爆炸效果
if (mBubbleState == BUBBLE_STATE_DISMISS && mCurDrawableIndex < mBurstBitmapsArray.length) {
mBurstRect.set((int) (mBubMovableCenter.x - mBubMovableRadius),
(int) (mBubMovableCenter.y - mBubMovableRadius),
(int) (mBubMovableCenter.x + mBubMovableRadius),
(int) (mBubMovableCenter.y + mBubMovableRadius)
);
canvas.drawBitmap(mBurstBitmapsArray[mCurDrawableIndex], null, mBurstRect, mBurstPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mBubbleState != BUBBLE_STATE_DISMISS) {
mDist = (float) Math.hypot(event.getX() - mBubFixedCenter.x, event.getY() - mBubFixedCenter.y);
Log.d(TAG, "mDist: " + (mDist < mBubbleRadius + MOVE_OFFSET));
if (mDist < mBubbleRadius + MOVE_OFFSET) {
mBubbleState = BUBBLE_STATE_CONNECT;
} else {
mBubbleState = BUBBLE_STATE_DEFAULT;
}
}
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "mBubbleState: " + mBubbleState);
if (mBubbleState != BUBBLE_STATE_DISMISS && mBubbleState != BUBBLE_STATE_DEFAULT) {
mDist = (float) Math.hypot(event.getX() - mBubFixedCenter.x, event.getY() - mBubFixedCenter.y);
Log.d(TAG, "mDist: " + mDist);
mBubMovableCenter.set(event.getX(), event.getY());//设置移动气泡的坐标
mBubFixedRadius = mBubbleRadius - mDist / 8;//缩小固定气泡的半径
if (mDist < mMaxDist + MOVE_OFFSET) {
mBubbleState = BUBBLE_STATE_CONNECT;
} else {
mBubbleState = BUBBLE_STATE_APART;//将气泡变为断开状态
}
}
invalidate();
break;
case MotionEvent.ACTION_UP:
if (mBubbleState == BUBBLE_STATE_CONNECT) {
startBubbleResetAnim();//回弹效果
} else if (mBubbleState == BUBBLE_STATE_APART) {
startBubbleBurstAnim();//爆炸动画
}
break;
}
return true;
}
//回弹动画
private void startBubbleResetAnim() {
ValueAnimator animator = ValueAnimator.ofObject(new PointFEvaluator(), mBubMovableCenter, mBubFixedCenter);
animator.setDuration(300);
animator.setInterpolator(new OvershootInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBubMovableCenter = (PointF) animation.getAnimatedValue();
invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBubbleState = BUBBLE_STATE_DEFAULT;
}
});
animator.start();
}
//爆炸动画
private void startBubbleBurstAnim() {
mBubbleState = BUBBLE_STATE_DISMISS;
ValueAnimator animator = ValueAnimator.ofInt(0, mBurstBitmapsArray.length * 2);
animator.setDuration(1000);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurDrawableIndex = (int) animation.getAnimatedValue();
invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBubMovableCenter.set(mBubFixedCenter.x, mBubFixedCenter.y);
mBubbleState = BUBBLE_STATE_DEFAULT;
}
});
animator.start();
}
}
附件相关表附件主要视图:fnd_attached_docs_form_vl附件主要表:fnd_attached_documents附件表:fnd_documents附件数据类型表:fnd_document_datatypes附件短文本表:fnd_documents_short_text附件长文本表:fnd_documents_long_text附件文件表:fnd_lobsRelat...
getContentPanepublic Container getContentPane()返回此窗体的 contentPane 对象API格式是这样写的,我觉像是除去边框,保存里面的内容!转载于:https://www.cnblogs.com/WillAndInsist/archive/2010/03/17/1687940.html...
https://blogs.msdn.microsoft.com/alejacma/2007/10/31/cryptoapi-tracer-script/我得多少年才能学会这种写法。转载于:https://www.cnblogs.com/suanguade/p/6119805.html
@property (nonatomic, assign) NSString *title;什么是assign,copy,retain之间的区别?assign: 简单赋值,不更改索引计数(Reference Counting)。copy: 建立一个索引计数为1的对象,然后释放旧对象retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1...
修改webview图片适配的代码,是使用js去修改图片适配屏幕宽度:// 网络请求加载的数据,进行字典转模型NSDictionary *dict = [result objectForKey:@"data"];HQNewsDetailModel *model = [HQNewsDetailModel mj_objectWithKeyValues:dict];/**...
后台目前每个模块生成的目标文件都放在make目录下面,逐个去删除很麻烦,需要在src这一级目录一次性删除所有*.o文件;find . -name "*.o" -exec rm {} \;命令说明:可以上面的rm换成其他命令如ls;转载于:https://www.cnblogs.com/skiing886/p/7608601.html...
SAE自2011-7-10日起,将全面支持SVN代码部署,用户不仅可以通过任何SVN客户端部署代码,而且SAE现有的代码部署方式也已经对接应用的SVN仓库,即使不使用SVN客户端部署代码,也保证了代码版本。 如:通过在线代码编辑器(http://sdk.tools.sinaapp.com)等方式对代码的修改、部署等操作也会像svn commit一样,产生一个新版本。通过SVN客户...
C#线程优先级的必要性:如果在应用程序中有多个线程在运行,但一些线程比另一些线程重要,该怎么办在这种情况下,可以在一个进程中为不同的线程指定不同的优先级。一般情况下,如果有优先级较高的线程在工作,就不会给优先级较低的线程分配任何时间片,其优点是可以保证给接收用户输入的线程指定较高的优先级。在大多数的时间内,这个线程什么也不做,而其他线程则执行它们的任务。但是,如果用户输入了信息,这个线程就立即获...
1. CentOS操作安装好了以后,系统自带了openJDK,先查看相关的安装信息:$rpm -qa | grep javatzdata-java-2013b-1.el6.noarchjava-1.6.0-openjdk-1.6.0.0-1.61.1.11.11.el6_4.x86_64java-1.7.0-openjdk-1.7.0.19-2.3.9.1.el6_4.x86_642. 可以用ja...
RAC添加节点:环境:OS:OEL5.6RAC:10.2.0.1.0原有rac1,rac2两个节点。如今要添加rac3节点:操作过程:改动三个节点上的/etc/hosts文件192.168.90.2rac1192.168.90.5rac2192.168.90.6rac3192.168.91.3rac1-priv192.168....
方便学习之用...天气预报Web服务,数据来源于中国气象局EndpointDiscoWSDLIP地址来源搜索 WEB 服务(是目前最完整的IP地址数据)EndpointDiscoWSDL随机英文、数字和中文简体字 WEB 服务EndpointDiscoWSDL中国邮政编码 <-> ...
转载地址:http://wenku.baidu.com/view/015c8549c850ad02df804105.html一、应用背景在许多情况下,我们并没有足够的I2C总线,本文主在介绍如何利用Linux内核中的i2c-gpio模块,利用2条GPIO线模拟i2c总线,并挂载设备二、思路先通过对i2c-gpio所定义的结构体初始化(包括初始化i2c的2条线,频