最近好多事情,原本的博客之路耽搁了,现在继续
上次的酷狗 只是个首页样子,这次我们将完成
- 滑动页面切换
- 下拉放大listview
- 歌词页面的滑动返回和出现
一。滑动页面切换
先来看下效果图
分析下。写代码之前都是分析,分析步骤和自己可能想得到的情形再奋键图码,是吧。
1.出现的时候要一个动画,这个动画怎么做呢?立马想到的是①overridePendingTransition;再想想的话还有②android:windowAnimationStyle,这个是不是style里面设置下也有这效果啊。
第②种就不演示了这里使用第二种
<item name="android:windowAnimationStyle">@style/AnimationActivity</item>
<style name="AnimationActivity" parent="@android:style/Animation.Translucent">
<item name="android:windowEnterAnimation">@anim/slide_right_in</item>
<item name="android:windowExitAnimation">@anim/slide_right_out</item>
<!--
<item name="android:activityOpenEnterAnimation">@anim/slide_right_in</item>
<item name="android:activityOpenExitAnimation">@anim/slide_left_out</item>
<item name="android:activityCloseEnterAnimation">@anim/slide_left_in</item>
<item name="android:activityCloseExitAnimation">@anim/slide_right_out</item>
-->
</style>
android:windowEnterAnimation 只要这个就可以了,outerAnimation设置了也没效果。可以自己都去试试。
滑动消失。在上一节咱使用了v4包里的ViewDragHelper,这次呢还是用它。
分析下。滑动的时候要解决上下 和左右滑动冲突的问题。所以onInterceptTouchEvent方法必须这种考虑。第一篇已经处理了好多次滑动冲突了,而且项目中经常要用到,这也算是开发必备的知识吧。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
int index = MotionEventCompat.getActionIndex(ev);
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
if (mActivePointerId == INVALID_POINTER) {
break;
}
mInitialMotionX = MotionEventCompat.getX(ev, index);
mInitialMotionY = MotionEventCompat.getY(ev, index);
break;
case MotionEvent.ACTION_MOVE:
int lastIndex = MotionEventCompat.findPointerIndex(ev,
mActivePointerId);
final float lastX = MotionEventCompat.getX(ev, lastIndex);
final float lastY = MotionEventCompat.getY(ev, lastIndex);
final float destX = Math.abs(lastX - mInitialMotionX);
final float destY = Math.abs(lastY - mInitialMotionY);
/* 加上判断是否不能拦截处理,比如viewpager,horizontalScrollview */
if (destX > destY && !requestNotIntercept) {
/* 传给getViewHorizontalDragRange拦截 */
if (lastX - mInitialMotionX > mDragHelper.getTouchSlop())
return mDragHelper.shouldInterceptTouchEvent(ev);
else
return false;
} else {
return false;
}
case MotionEvent.ACTION_UP:
break;
}
return mDragHelper.shouldInterceptTouchEvent(ev);
}
代码很熟悉,也就不分析了,值得注意的是加上了requestNotIntercept,就可以让子view控制是不是要处理了。
二.下拉放大listview
下拉放大。我是参照了一位仁兄的思想。后来没有找到链接。
他的思想呢就是不断的设置listview的headerview的高度。
后来呢 我自己的思想就是就是通过自定义drawable 不断的改变bitmap的大小,单感觉效果不是很理想,不理想的原因呢是因为我把放大的view和listview分开了。
要是也放大listview的headerview上面去,效果应该是一样的。
代码里面注释比较详细,直接上代码了
/**
* 空间头部变大view 用这个listview height必须设置match_parent
*
* @author hupihuai
*
*/
public class PullZoomListView extends ListView implements OnClickListener {
/* 头部View 的容器 */
private FrameLayout mHeaderContainer;
/* 头部View 的图片 */
private ImageView mHeaderImg;
/* 屏幕的高度 */
private int mScreenHeight;
/* 屏幕的宽度 */
private int mScreenWidth;
private int mHeaderHeight;
/* 无效的点 */
private static final int INVALID_POINTER = -1;
/* 滑动动画执行的时间 */
private static final int MIN_SETTLE_DURATION = 200; // ms
/* 定义了一个时间插值器,根据ViewPage控件来定义的 */
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
/* 记录上一次手指触摸的点 */
private float mLastMotionX;
private float mLastMotionY;
private float mInitialMotionY;
/* 当前活动的点Id,有效的点的Id */
protected int mActivePointerId = INVALID_POINTER;
/* 开始滑动的标志距离 */
private int mTouchSlop;
private Scroller mScroller;
/* 放大的倍数 */
private float mScale;
/* 上一次放大的倍数 */
private float mLastScale;
/* 最大放大的倍数 */
private final float mMaxScale = 2.0f;
/* 是否需要禁止ListView 的事件响应 */
private boolean isNeedCancelParent;
/* 下拉刷新的阈值 */
private final float REFRESH_SCALE = 1.20F;
/* 刷新监听 */
private OnRefreshListener mRefreshListener;
/* header image 最大高度 */
private float HEADER_IMAGEVIEW_MAX_HEIGHT;
/* 是否弹性拖动 */
private boolean isSticky;
/* 是否放大图片 */
private boolean isScale;
/* 按下的y位置 */
private int mInitialScrollY;
/* 弹性拖动比例 */
private final float stickyScale = 0.5f;
private View footerView;
/* 能否加载更多 */
private boolean isCanLoadMore = true;
/* 能否刷新 只有等到刷新完成才能再次刷新 */
private boolean isCanRefresh = true;
private int lastVisiblePosition;
public PullZoomListView(Context context) {
super(context);
init(context);
}
public PullZoomListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public PullZoomListView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
/* 这里获取的是一个无用值,可忽略 */
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = ViewConfigurationCompat
.getScaledPagingTouchSlop(configuration);
mScroller = new Scroller(getContext(), sInterpolator);
/* 创建头部View 的容器 */
mHeaderContainer = new FrameLayout(context);
/* 获取屏幕的像素值 */
DisplayMetrics metrics = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay()
.getMetrics(metrics);
mScreenHeight = metrics.heightPixels;
mScreenWidth = metrics.widthPixels;
/* 设置头部View 的初始大小 */
mHeaderHeight = (int) ((11 * 1.0f / 16) * mScreenWidth);
HEADER_IMAGEVIEW_MAX_HEIGHT = mHeaderHeight * mMaxScale + 3;
LayoutParams absLayoutParams = new LayoutParams(mScreenWidth,
mHeaderHeight);
mHeaderContainer.setLayoutParams(absLayoutParams);
/* 创建图片显示的View */
mHeaderImg = new ImageView(context);
FrameLayout.LayoutParams imgLayoutParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mHeaderImg.setScaleType(ImageView.ScaleType.CENTER);
mHeaderImg.setLayoutParams(imgLayoutParams);
mHeaderContainer.addView(mHeaderImg);
/* 增加头部View */
addHeaderView(mHeaderContainer);
/* 添加footer */
footerView = LayoutInflater.from(getContext()).inflate(
R.layout.pull_zoom_listview_footer_layout, null);
footerView.setOnClickListener(this);
// addFooterView(footerView);
/* 设置监听事件 */
setOnScrollListener(new InternalScrollerListener());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/* 处理事件用 */
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
/* 计算 x,y 的距离 */
int index = MotionEventCompat.getActionIndex(ev);
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
if (mActivePointerId == INVALID_POINTER)
break;
mLastMotionX = MotionEventCompat.getX(ev, index);
mInitialMotionY = mLastMotionY = MotionEventCompat.getY(ev, index);
abortAnimation();
/* 计算算一次放缩的比例 */
mLastScale = (this.mHeaderContainer.getBottom() / this.mHeaderHeight);
/* 当按下的时候把这个标志为设为有效 */
isNeedCancelParent = true;
break;
case MotionEvent.ACTION_MOVE:
int indexMove = MotionEventCompat.getActionIndex(ev);
mActivePointerId = MotionEventCompat.getPointerId(ev, indexMove);
final float y = MotionEventCompat.getY(ev, indexMove);
float dy = y - mLastMotionY;
if (mActivePointerId == INVALID_POINTER) {
/* 这里相当于松手 */
finishPull();
isNeedCancelParent = true;
/* 这是一个关键值,通过这个值来判断是否需要放大图片 */
} else {
if (mHeaderContainer.getBottom() >= mHeaderHeight) {
if (!isScale)
isScale = true;
ViewGroup.LayoutParams params = this.mHeaderContainer
.getLayoutParams();
float f = ((dy + this.mHeaderContainer.getBottom())
/ this.mHeaderHeight - this.mLastScale)
/ 2.0F + this.mLastScale;
if ((this.mLastScale <= 1.0D) && (f <= this.mLastScale)) {
if (getChildAt(getChildCount() - 1).getBottom() < getMeasuredHeight()) {
stickyScroll(y, dy);
return true;
} else {
if (params.height != this.mHeaderHeight) {
params.height = this.mHeaderHeight;
this.mHeaderContainer.setLayoutParams(params);
}
return super.onTouchEvent(ev);
}
}
/* 这里设置紧凑度 */
dy = dy * 0.5f * (mHeaderHeight * 1.0f / params.height);
mLastScale = (dy + params.height) * 1.0f / mHeaderHeight;
mScale = clamp(mLastScale, 1.0f, mMaxScale);
params.height = (int) (mHeaderHeight * mScale);
mHeaderContainer.setLayoutParams(params);
mLastMotionY = y;
/* 这里,如果图片有放大,则屏蔽ListView 的其他事件响应 */
if (isNeedCancelParent) {
isNeedCancelParent = false;
MotionEvent motionEvent = MotionEvent.obtain(ev);
motionEvent.setAction(MotionEvent.ACTION_CANCEL);
super.onTouchEvent(motionEvent);
}
return true;
} else if (!canChildScrollDown()) {
View childAt = getChildAt(getChildCount() - 1);
if (childAt != null) {
/* 什么时候交给父类 */
if (childAt.getBottom() >= getMeasuredHeight()
&& dy > 0 && mInitialScrollY >= getScrollY()) {
return super.onTouchEvent(ev);
}
}
stickyScroll(y, dy);
return true;
}
}
break;
case MotionEvent.ACTION_UP:
/* 结束事件响应,做相应的操作 */
if (isScale) {
finishPull();
}
if (isSticky) {
final int diffY = mInitialScrollY - getScrollY();
mScroller.startScroll(getScrollX(), getScrollY(), 0, diffY);
invalidate();
/* 这里屏蔽ListView 的其他事件响应 */
MotionEvent motionEvent = MotionEvent.obtain(ev);
motionEvent.setAction(MotionEvent.ACTION_CANCEL);
super.onTouchEvent(motionEvent);
}
isScale = false;
isSticky = false;
mInitialScrollY = 0;
break;
case MotionEvent.ACTION_POINTER_UP:
/*
* 这里需要注意,多点处理,这里的处理方式是:如果有两个手指按下,抬起的是后按下的手指,则不做处理
* 如果抬起的是最先按下的手指,则复原图片效果。
*/
int pointUpIndex = MotionEventCompat.getActionIndex(ev);
int pointId = MotionEventCompat.getPointerId(ev, pointUpIndex);
if (pointId == mActivePointerId) {
/* 松手执行结束拖拽操作 */
/* 结束事件响应,做相应的操作 */
finishPull();
}
break;
}
return super.onTouchEvent(ev);
}
private void stickyScroll(final float y, final float dy) {
/* 反弹 效果 */
final float diffY = Math.abs(y - mInitialMotionY);
if (diffY > mTouchSlop) {
if (isSticky == false) {
isSticky = true;
mInitialScrollY = getScrollY();
}
mLastMotionY = y;
final float scrollY = getScrollY() - dy * stickyScale;
mLastMotionX += scrollY - (int) scrollY;
scrollTo(getScrollX(), (int) scrollY);
}
}
@Override
public void computeScroll() {
// TODO Auto-generated method stub
if (mScroller.computeScrollOffset()) {
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
}
invalidate();
}
}
/**
* 还能否向下滚动
*
* @param mTarget
* @return
*/
public boolean canChildScrollDown() {
if (android.os.Build.VERSION.SDK_INT < 14) {
final int lastItemPosition = getCount() - 1;
final int lastVisiblePosition = getLastVisiblePosition();
if (lastVisiblePosition >= lastItemPosition) {
final int childIndex = lastVisiblePosition
- getFirstVisiblePosition();
final View lastVisibleChild = getChildAt(childIndex);
if (lastVisibleChild != null
&& lastVisibleChild.getBottom() < getBottom()) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
return ViewCompat.canScrollVertically(this, 1);
}
}
private void abortAnimation() {
if (!mScroller.isFinished())
mScroller.abortAnimation();
}
private void finishPull() {
mActivePointerId = INVALID_POINTER;
/* 这是一个阈值,如果成立,则表示图片已经放大了,在手指抬起的时候需要复原图片 */
if (mHeaderContainer.getBottom() > mHeaderHeight) {
// 这里是下拉刷新的阈值,当达到了,则表示需要刷新,可以添加刷新动画/
if (mScale > REFRESH_SCALE) {
if (mRefreshListener != null && isCanRefresh) {
mRefreshListener.onRefresh();
isCanRefresh = false;
}
}
// 图片复原动画/
pullBackAnimation();
}
}
/**
* 这是属性动画的知识,不懂的可以去看看属性动画的知识
*/
private void pullBackAnimation() {
ValueAnimator pullBack = ValueAnimator.ofFloat(mScale, 1.0f);
pullBack.setInterpolator(sInterpolator);
pullBack.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
LayoutParams params = (LayoutParams) mHeaderContainer
.getLayoutParams();
params.height = (int) (mHeaderHeight * value);
mHeaderContainer.setLayoutParams(params);
}
});
pullBack.setDuration((long) (MIN_SETTLE_DURATION * mScale));
pullBack.start();
}
/**
* 通过事件和点的 id 来获取点的索引
*
* @param ev
* @param id
* @return
*/
private int getPointerIndex(MotionEvent ev, int id) {
int activePointerIndex = MotionEventCompat.findPointerIndex(ev, id);
if (activePointerIndex == -1)
mActivePointerId = INVALID_POINTER;
return activePointerIndex;
}
public void setOnRefreshListener(OnRefreshListener l) {
mRefreshListener = l;
}
public ImageView getHeaderImageView() {
return this.mHeaderImg;
}
public void setHeaderPicture(Bitmap bp) {
Bitmap newBp = Bitmap.createScaledBitmap(bp, mScreenWidth,
(int) HEADER_IMAGEVIEW_MAX_HEIGHT, true);
if (newBp != bp) {
bp.recycle();
}
mHeaderImg.setImageBitmap(newBp);
}
public void setHeaderPicture(int resId) {
Bitmap bp = BitmapFactory.decodeResource(getResources(), resId);
Bitmap newBp = Bitmap.createScaledBitmap(bp, mScreenWidth,
(int) HEADER_IMAGEVIEW_MAX_HEIGHT, true);
if (newBp != bp) {
bp.recycle();
}
mHeaderImg.setImageBitmap(newBp);
}
private float clamp(float value, float min, float max) {
return Math.min(Math.max(value, min), max);
}
private class InternalScrollerListener implements OnScrollListener {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
float diff = mHeaderHeight - mHeaderContainer.getBottom();
if ((diff > 0.0F) && (diff < mHeaderHeight)) {
int i = (int) (0.3D * diff);
mHeaderImg.scrollTo(0, -i);
} else if (mHeaderImg.getScrollY() != 0) {
mHeaderImg.scrollTo(0, 0);
}
if (getLastVisiblePosition() == totalItemCount - 3
&& lastVisiblePosition < getLastVisiblePosition()) {
if (mRefreshListener != null && isCanLoadMore) {
if (footerView.getParent() != null
&& footerView.getVisibility() == View.VISIBLE)
mRefreshListener.onLoadMore();
isCanLoadMore = true;
}
}
lastVisiblePosition = getLastVisiblePosition();
}
}
public int getScrolledY() {
View c = getChildAt(0);
if (c == null) {
return 0;
}
int firstVisiblePosition = getFirstVisiblePosition();
int top = c.getTop();
int headerHeight = 0;
if (firstVisiblePosition >= 1) {
headerHeight = mHeaderHeight;
}
return -top + firstVisiblePosition * c.getHeight() + headerHeight;
}
public void setHeaderContentView(ViewGroup contentView) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT);
mHeaderContainer.addView(contentView, params);
}
public void setCanLoadMore(boolean isCanLoadMore) {
this.isCanLoadMore = isCanLoadMore;
}
public void setCanRefresh(boolean isCanRefresh) {
this.isCanRefresh = isCanRefresh;
}
public interface OnRefreshListener {
public void onRefresh();
public void onLoadMore();
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
mRefreshListener.onLoadMore();
}
/**
* 设置是否需要加载更多功能
*
* @param needLoadMore
*/
public void isNeedLoadMore(boolean needLoadMore) {
if (needLoadMore) {
if (footerView.getParent() != null) {
removeFooterView(footerView);
}
} else {
if (footerView.getParent() == null) {
addFooterView(footerView);
}
}
}
}
注意的几点
1.临界值的if (mHeaderContainer.getBottom() >= mHeaderHeight),也就是headerview的bottom和headerview的高度判断。
2.其它的呢就是基本的view的滑动几个操作类了Scroller,ViewConfigurationCompat ,VelocityTracker(这里没用到,因不需要快速滑动)
三。歌词页面的滑动返回和出现
再第一个的滑动返回的基础上实现这个功能就比较简单了
也是配置下windowEnterAnimation 然后在第一个滑动返回里面扩展下。
public void setOnAnimListener(OnAnimListener animListener) {
this.mAnimListener = animListener;
}
/**
* 定制动画的接
*/
public interface OnAnimListener {
/**
* 重写这个方法来定制动
*
* @param view
* @param offSets
* @param offSetPix
*/
void onAnimationSet(View view, float offSet, int offSetPix);
}
提供一个接口给外部调用,都是这么写的吧,是吧。然后在动画时判断下
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
// TODO Auto-generated method stub
mSlideOffset = (float) left / getMeasuredWidth();
//自定义动画,实现紫的动画
if (mAnimListener != null)
mAnimListener.onAnimationSet(changedView, mSlideOffset, dx);
if (mSlideOffset == 1)
mActivity.finish();
else
invalidate();
}
在activity里实现下
v4SlidingLayout.setOnAnimListener(new V4SlidingLayout.OnAnimListener() {
@Override
public void onAnimationSet(View view, float offSet, int offSetPix) {
// TODO Auto-generated method stub
float degrees = 30 * offSet;
RotateAnimation rA = new RotateAnimation(degrees, degrees, view
.getWidth() / 2, view.getHeight());
rA.setFillAfter(true);
rA.setDuration(0);
view.startAnimation(rA);
}
});
beautiful !! 发现没有 要是实现微信那种删除是不是也很容易了,相信你们可以的。嘿嘿
但是我研究的时候发现酷狗不是用activity实现的,他是在一个view里面实现的。这就跟业务有关系了。咱就先这么现实,等做歌词播放时来看看那种方式好。
酷狗系列还没完结哦。。。
好。。。。
代码传送
点击下载code