酷狗界面仿照篇二

最近好多事情,原本的博客之路耽搁了,现在继续
上次的酷狗 只是个首页样子,这次我们将完成

  1. 滑动页面切换
  2. 下拉放大listview
  3. 歌词页面的滑动返回和出现

一。滑动页面切换
先来看下效果图
这里写图片描述

分析下。写代码之前都是分析,分析步骤和自己可能想得到的情形再奋键图码,是吧。
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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值