一般一些项目中都少不了一些头部背景图,但是如果背景图静态的现实并不能呈现出与用户操作的交互感,所以要想办法让背景图动起来,qq的一些交互感我很喜欢,比如他的个人详情界面的背景图就是可以下拉扩展,并在扩展到一定程度中可以放大图片。其设计原理就是先隐藏头部和底部的一些视图,然后在下拉过程中慢慢把隐藏的部分显示出来,到完整显示后就可以放大图片,这样设计的好处就是:1、节省一些屏幕空间,不影响正常的操作内容显示。2、增加了趣味性,能更好的提升界面与用户的互动性。
既然知道了原理就让我们自己来动手撸一个这样的控件出来吧。在此之前我也了解了一些别人实现的头部图片方法,一般是使用重写ScrollView实现的,但是其中的滑动冲突并没有解决,这自然满足不了对其他项目的兼容性,所以我实现的方法是重写NestedScrollView,使用原因是这个控件已经处理好了与子控件的滑动冲突。
先上图:
仔细看效果,这个背景图是先扩展然后再放大的。和qq的背景图的效果差不多。现在我们来分析一下怎么实现的
第一步就是怎么一开始把图片的顶部和底部隐藏
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取头部视图的原始宽高
if (viewWidth <= 0 || viewHeight <=0) {
viewWidth = headView.getMeasuredWidth();
viewHeight = headView.getMeasuredHeight();
}
//绘制视图时隐藏头部View的顶部和底部
if (hideHeight==0){
hideHeight=viewHeight/hideRatio;
ViewGroup.LayoutParams layoutParams = headView.getLayoutParams();
((MarginLayoutParams) layoutParams).setMargins(0, -hideHeight, 0,-hideHeight);
headView.setLayoutParams(layoutParams);
}
}
这里我采取的方式是通过在绘制时调整其顶部与底部的边距实现的。就这几行代码,就简单的实现了头部图片部分的隐藏
但是在此之前还要注意一下头部View的获取:
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 不可过度滚动,否则上移后下拉会出现部分空白的情况
setOverScrollMode(OVER_SCROLL_NEVER);
// 获得默认第一个view
if (getChildAt(0) != null && getChildAt(0) instanceof ViewGroup && headView == null) {
ViewGroup mViewGroup = (ViewGroup) getChildAt(0);
if (mViewGroup.getChildCount() > 0) {
headView = mViewGroup.getChildAt(0);
}
}
}
实现的方法很简单,就是获取其内容中的第一个子视图,因为滑动视图的特性,其最近的子视图是ViewGroup,所以则获取这个ViewGroup的第一个子View
隐藏部分实现后接下来就是实现其下拉操作的部分:
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (viewWidth <= 0 || viewHeight <=0) {
viewWidth = headView.getMeasuredWidth();
viewHeight = headView.getMeasuredHeight();
}
if (headView == null || viewWidth <= 0 || viewHeight <= 0) {
return super.onTouchEvent(ev);
}
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
if (!isUnfolding) {
if (getScrollY() == 0) {
pullY = ev.getY();//滑动到顶部时,记录位置
} else {
break;
}
}
int distance = (int) ((ev.getY() - pullY)*zoomRatio);
if (distance < 0) break;//若往下滑动
isUnfolding = true;
if (hideHeight>distance){
unfoldImage(distance);
unfoldHeight=distance;
return true;
}
setZoom(distance-hideHeight);
return true;
case MotionEvent.ACTION_UP:
isUnfolding = false;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (!isUnfolding){
replyView();
}
}
}, 100);
break;
}
return super.onTouchEvent(ev);
}
如果其子视图没有初始化宽高则初始化宽高,获取头部视图失败则过滤其滑动事件,只需要重写滑动事件即可,当滑动到顶部的时候记录下滑动的第一个位置,否则继续执行其滑动事件,当到达顶部并往上滑的时候传递滑动事件,正常使用滑动视图的功能,当到达顶部并下拉的时候开始进行扩展放大。首先是进行扩展,当图片完全展示出来后进行放大处理。当手指放开便进行视图返回操作,这里我加了个延时,目的是操作时更自然点。这个主要是介绍一下处理的逻辑,接下来分析一下扩展、放大、回弹的实现内容
/**
* 扩展头部视图
* @param distance 顶部和底部扩展的距离
*/
private void unfoldImage(float distance) {
ViewGroup.LayoutParams layoutParams = headView.getLayoutParams();
//下拉时保持居中,设置顶部和底部的边距让隐藏的部分视图显示出来,达到扩展目的
((MarginLayoutParams) layoutParams).setMargins(-(layoutParams.width
- viewWidth) / 2>0?0:-(layoutParams.width - viewWidth) / 2,
(int)(distance-hideHeight), 0,(int)(distance-hideHeight));
headView.setLayoutParams(layoutParams);
}
扩展头部视图的实现就是根据下拉的距离来改变顶部和底部的边距,达到隐藏部分慢慢扩展显示的效果
/**
* 放大头部View
* @param distance 放大的距离
*/
private void setZoom(float distance) {
float scaleTimes = (float) ((viewWidth+distance)/(viewWidth*1.0));
// 如超过最大放大倍数,直接返回
if (scaleTimes > maxZoomRatio) return;
ViewGroup.LayoutParams layoutParams = headView.getLayoutParams();
layoutParams.width = (int) (viewWidth + distance);
layoutParams.height = (int)(viewHeight*((viewWidth+distance)/viewWidth));
// 设置控件水平居中
((MarginLayoutParams) layoutParams).setMargins(-(layoutParams.width - viewWidth) / 2, 0, 0, 0);
headView.setLayoutParams(layoutParams);
isZoom=true;
}
放大的效果即调整头部视图的宽度和高度来实现的,在放大的同时要注意保持头部空间的居中特性。因为在拉伸图片放大时高度会完全显示,但是宽度会超过屏幕,要是不居中,放大的焦点就会处于左上角,居中后放大的焦点将处于控件中心。
/**
* 头部视图还原
*/
private void replyView() {
/**
* 如果头部视图被放大,添加动画还原放大的头部视图
*/
if (isZoom){
final float distance = headView.getMeasuredWidth() - viewWidth;
// 设置动画
ValueAnimator anim = ObjectAnimator.ofFloat(distance, 0.0F).setDuration((long) (distance * replyTimeRatio));
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setZoom((Float) animation.getAnimatedValue());
}
});
anim.start();
}
/**
* 将扩展出来的头部视图还原
*/
ValueAnimator unfold = ObjectAnimator.ofFloat(unfoldHeight, 0.0F).setDuration((long) (unfoldHeight));
unfold.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
unfoldImage((Float) animation.getAnimatedValue());
}
});
unfold.start();
unfoldHeight=0;
}
头部视图的回弹实现就是通过给头部控件添加一个动画实现的,内容比较简单,但是需要注意当图片只是扩展时不需要给视图添加放大回弹效果
这样主要的内容就分析完成,实现起来还是比较简单的,有兴趣可以看下源码
参考博客地址:http://blog.csdn.net/anyfive/article/details/52575262