徐徐展开的画卷
写了个简单的自定义控件,能把View像打开画卷一样徐徐展开的ViewGroup,山河画卷.放在这个ViewGroup里面的View可以被这个ViewGroup控件徐徐打开,然后图穷匕首现
效果如下
“1”是可以更换的图片,最初想法是一个向右的箭头.
继承FrameLayout,因为FrameLayout耗资源少,而且onLayout什么的都写好了
//像山河画卷一样展开的View
public class PictureScrollView extends FrameLayout {
public PictureScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public PictureScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PictureScrollView(Context context) {
super(context);
init();
}
先把子控件画出来,右侧不让显示的地方我们用画笔擦除调,需要申明一个擦除画笔,和一个画卷的引导卷轴,即效果图中的”1”
//清除画笔
private Paint mClearPaint;
// Reel卷轴,指示当前展开到的位置
private Bitmap reel;
// 控制卷轴的宽高
private float mReelWidth, mReelHeight;
//卷轴绘制到画板的区域
private RectF dstReel = new RectF();
//卷轴图片源,即reel的矩形区域
private Rect srcReel;
private void init() {
mClearPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mClearPaint.setColor(Color.BLUE);
//画笔的混合模式,为清除像素
mClearPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
reel = BitmapFactory.decodeResource(getResources(), R.drawable.icon_select);
srcReel = new Rect(0, 0, reel.getWidth(), reel.getHeight());
Log.d("px", "srcReel-->" + srcReel.toString());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
mClearRect.set(mProgress, 0, mWidth, getMeasuredHeight());
// 设置卷轴的宽高
if (mReelWidth == 0) {
// 按图片的实际宽高
// mReelWidth = reel.getWidth();
// mReelHeight = reel.getHeight();
// 或者应该指定按控件的百分比,而不是完全按卷轴图片的宽高来
mReelHeight = mHeight / 10;
mReelWidth = reel.getWidth() * mReelHeight / reel.getHeight();
}
//设置卷轴绘画到的区域,应位于当前展开进度的正中心
dstReel.set(mProgress - mReelWidth / 2 , mHeight / 2 - mReelHeight / 2, mReelWidth / 2 + mProgress, mHeight / 2
+ mReelHeight / 2);
Log.d("px", "dstReel-->" + dstReel.toString());
}
mProgress指目前画卷展开的进度,就是x轴的距离.mClearRect等声明在下面
绘制过程,super.dispatchDraw是绘制子View..在用擦除画笔时必须先保存为图层,否则将擦除一切.
private RectF rect = new RectF(), mClearRect = new RectF();
@Override
protected void dispatchDraw(Canvas canvas) {
rect.set(canvas.getClipBounds());
// canvas.drawColor(0x00ffffff);
int saveCount = canvas.saveLayer(rect, null, Canvas.ALL_SAVE_FLAG);//这里null在xml预览报错,要不报错传个全局new paint吧.
super.dispatchDraw(canvas);
canvas.drawRect(mClearRect, mClearPaint);
canvas.restoreToCount(saveCount);
if (showReel) {
canvas.drawBitmap(reel, srcReel, dstReel, null);
}
}
余下代码
// 启动画卷模式
private boolean startDrawProgress = false;
// 从0到完全展开的时间
private long completelyOpenTime = 2000;
//是否显示引导卷轴
private boolean showReel = true;
public void setProgress(float progress) {
this.mProgress = progress;
mClearRect.set(progress, 0, mWidth, mHeight);
invalidate();
}
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
switch (e.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.d("px", "ACTION_DOWN");
// 点到卷轴,则启动画卷模式
if (startDrawProgress = checkEventAtReel(e)) {
mProgress = e.getX();
mClearRect.set(mProgress, 0, mWidth, mHeight);
dstReel.left = mProgress - mReelWidth / 2;
dstReel.right = mProgress + mReelWidth / 2;
if (observer != null) {
observer.onScroll(mProgress);
}
invalidate();
}
// 点击到进度外看不见的地方,拒绝该事件,也不向下传递事件
else if (e.getX() > mProgress) {
return false;
}
break;
case MotionEvent.ACTION_MOVE:
Log.d("px", "ACTION_MOVE");
if (startDrawProgress) {
mProgress = e.getX();
mClearRect.set(mProgress, 0, mWidth, mHeight);
dstReel.left = mProgress - mReelWidth / 2;
dstReel.right = mProgress + mReelWidth / 2;
if (observer != null) {
observer.onScroll(mProgress);
}
invalidate();
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (startDrawProgress) {
if (mProgress > mWidth / 2) {
// 大于一半自动全部展开
openFromCurrentPosition();
} else {
// 小于一半自动全部收回
closeFromCurrentPosition();
}
}
break;
default:
break;
}
super.dispatchTouchEvent(e);
return true;
}
private ValueAnimator animator;
private ValueAnimator.AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
float v = (Float) animator.getAnimatedValue();
mClearRect.set(mProgress = v, 0, mWidth, getMeasuredHeight());
dstReel.left = mProgress - mReelWidth / 2;
dstReel.right = mProgress + mReelWidth / 2;
if (observer != null) {
observer.onScroll(mProgress);
}
invalidate();
}
};
// 大于一半自动全部展开
private void openFromCurrentPosition() {
if (animator != null) {
animator.cancel();
}
animator = ValueAnimator.ofFloat(mProgress, mWidth);
animator.addUpdateListener(updateListener);
animator.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
if (observer != null) {
observer.onOpen();
}
}
});
animator.setDuration((long) ((mWidth - mProgress) / mWidth * completelyOpenTime));
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
}
// 小于一半自动全部收回
private void closeFromCurrentPosition() {
if (animator != null) {
animator.cancel();
}
animator = ValueAnimator.ofFloat(mProgress, 0);
animator.addUpdateListener(updateListener);
animator.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
if (observer != null) {
observer.onClose();
}
}
});
animator.setDuration((long) (mProgress / mWidth * completelyOpenTime));
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
}
public boolean isShowReel() {
return showReel;
}
public void setShowReel(boolean showReel) {
this.showReel = showReel;
}
/**
* 检查事件是否在可点击的卷轴上
*
* @param e
* @return
*/
private boolean checkEventAtReel(MotionEvent e) {
// 卷轴不可见,则非
if (!showReel) {
return false;
}
if (e.getX() < dstReel.left) {
return false;
}
if (e.getX() > dstReel.right) {
return false;
}
if (e.getY() < dstReel.top) {
return false;
}
if (e.getY() > dstReel.bottom) {
return false;
}
return true;
}
/**
* 观察者,监控展开与收缩的过程
* @author user
*
*/
public interface Observer {
// 完全展开
void onOpen();
// 完全关闭
void onClose();
// 滚动中
void onScroll(float dx);
}
// 观察者,监听view的展开和收拢过程
private Observer observer;
public Observer getObserver() {
return observer;
}
public void setObserver(Observer observer) {
this.observer = observer;
}
观察者模式用来允许使用时拓展,比如
final PictureScrollView picture = (PictureScrollView) findViewById(R.id.pictureScrollView1);
picture.setObserver(new Observer() {
@Override
public void onScroll(float dx) {
//随着进度慢慢淡入
pictureScrollView.setAlpha(dx/pictureScrollView.getWidth());
}
@Override
public void onOpen() {
//完全展开后不允许再卷回去了
pictureScrollView.setShowReel(false);
}
@Override
public void onClose() {
}
});
实现点击卷轴自动展开,从右向左展开,竖向展开等等,可以修改一些代码做到
不是所有效果都需要自定义View,有时候一个Action也可以完成
百叶窗
下面是一个百叶窗效果的实现,利用View的绕x=a轴旋转变换
大致效果图是这样,但GIF没有我的手机上表现流畅
private ListView docScanListView;
private boolean isBlindAniming = false;
private Handler handler = new Handler();
/**
*
* @描述:百叶窗效果
*
* @作者 [pWX273343] 2015年11月6日
*/
private void doBlind() {
docScanListView.setVisibility(View.VISIBLE);
docScanListView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressLint("NewApi")
@Override
public void onGlobalLayout() {
docScanListView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (isBlindAniming) {
return;
} else {
isBlindAniming = true;
}
final int count = docScanListView.getChildCount();
// 设置初始状态,-90从开始出现
for (int i = 0; i < count; i++) {
final View view;
view = docScanListView.getChildAt(i);
view.setPivotX(view.getWidth() / 2f);
view.setPivotY(view.getHeight() / 2f);
view.setRotationX(90);
}
// 对每个item项做翻转动画
for (int i = 0; i < count; i++) {
final View view;
view = docScanListView.getChildAt(count - i - 1);
final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setDuration(250);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float v = (Float) animation.getAnimatedValue();
view.setScaleX(0.8f + 0.2f * v);
view.setScaleY(0.8f + 0.2f * v);
view.setRotationX(90 - 90 * v);
}
});
// 最后一个View动画添加结束监听
if (i == count - 1) {
animator.addListener(new SimpleAnimatorListener() {
public void onAnimationEnd(Animator animation) {
isBlindAniming = false;
}
});
}
handler.postDelayed(new Runnable() {
@Override
public void run() {
animator.start();
}
}, 100 * i);
}
}
});
}
/**
*
* @描述:百叶窗效果退出
*
* @param needDismiss
* 是否需要关闭弹窗
* @作者 [pWX273343] 2015年11月11日
*/
public void dismissBlind() {
if (isBlindAniming) {
return;
} else {
isBlindAniming = true;
}
if (docScanListView.getVisibility() != View.VISIBLE) {
isBlindAniming = false;
return;
}
final int count = docScanListView.getChildCount();
for (int i = 0; i < count; i++) {
final View view = docScanListView.getChildAt(i);
view.setPivotX(view.getWidth() / 2f);
view.setPivotY(view.getHeight() / 2f);
final ValueAnimator animator = ValueAnimator.ofFloat(1, 0);
animator.setDuration(250);
animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float v = (Float) animation.getAnimatedValue();
view.setScaleX(0.8f + 0.2f * v);
view.setScaleY(0.8f + 0.2f * v);
view.setRotationX(90 - 90 * v);
}
});
// 最后一个做完添加关闭弹窗
if (i == count - 1)
animator.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
isBlindAniming = false;
docScanListView.setVisibility(View.GONE);
}
});
handler.postDelayed(new Runnable() {
@Override
public void run() {
animator.start();
}
}, 100 * i);
}
}