仿最美应用-每日最美 钢琴律动效果(二)

来源:互联网 发布:ibatis sql xml 语法 编辑:程序博客网 时间:2024/04/29 15:54

一、可以侧拉刷新加载的ViewPager
首先需要添加ViewPager,这个是一个可以侧拉加载刷新的ViewPager,这里最美使用的是GitHub上的一个开源项目:
Android-PullToRefresh
修改我们的activity_main.xml布局文件:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:ptr="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#00aac6">    <com.handmark.pulltorefresh.library.extras.viewpager.PullToRefreshViewPager        android:id="@+id/pager"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_marginTop="50.0dip"        ptr:ptrAnimationStyle="rotate_and_anim"        ptr:ptrDrawable="@drawable/loading_1"        ptr:ptrMode="both"        ptr:ptrScrollingWhileRefreshingEnabled="false" />    <com.shine.niceapp.control.RhythmLayout        android:id="@+id/box_rhythm"        android:layout_width="match_parent"        android:layout_height="@dimen/rhythm_layout_height"        android:layout_alignParentBottom="true"        android:scrollbars="none">        <LinearLayout            android:layout_width="wrap_content"            android:layout_height="match_parent"            android:orientation="horizontal" />    </com.shine.niceapp.control.RhythmLayout></RelativeLayout>
  • ptrAnimationStyle设置侧拉到底部或顶部时出现的图案将要执行的动画方式,这里我选择的是旋转动画
  • ptrDrawable设置出现的图案,这里我设置成了loading_1这是从最美应用里拿来的图片
  • ptrMode设置所支持的上拉下拉方式,这里我选择上拉下拉都支持
  • ptrScrollingWhileRefreshingEnabled设置刷新时是否允许ViewPager滚动,这里我选择不允许

接下来就是为ViewPager创建一个适配器,这里使用的适配器是FragmentStatePagerAdapter所以需要一个Fragment,先来看下这个Fragment的布局:fragment_card.xml:

<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_margin="5dp"        android:background="@drawable/home_card_bg">    </RelativeLayout></FrameLayout>

其实就是一个FrameLayout中套了一层RelativeLayout,当然在最美应用的布局中,RelativeLayout里还有很多的控件的,但是这些对于我们来说并不是重点,所以我仅仅拿了最外层的2层布局,接下来看看这个Fragment中的内容:

public class CardFragment extends Fragment {    public static CardFragment getInstance(Card card) {        CardFragment fragment = new CardFragment();        Bundle bundle = new Bundle();        bundle.putSerializable("card", card);        fragment.setArguments(bundle);        return fragment;    }    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        return inflater.inflate(R.layout.fragment_card, null);    }}

同上,在此中本来是有很多加载数据的操作的,getInstance中的card就是数据源,但是对于我们来说并不需要,所以我只是简单的写了个getInstance,复写了onCreateView而已,之后便是适配器了

public class CardPagerAdapter extends FragmentStatePagerAdapter {    private List<Card> mCardList;    private List<Fragment> mFragments = new ArrayList();    public CardPagerAdapter(FragmentManager fragmentManager, List<Card> cardList) {        super(fragmentManager);        //使用迭代器遍历List,        Iterator iterator = cardList.iterator();        while (iterator.hasNext()) {            Card card = (Card) iterator.next();            //得到相应的Fragment实例并添加到List中            mFragments.add(CardFragment.getInstance(card));        }        mCardList = cardList;    }    public int getCount() {        return mFragments.size();    }    @Override    public Fragment getItem(int position) {        return mFragments.get(position);    }}

这只是一些很简单的代码,我想并不需要多说什么,最后修改MainActivity中的代码如下:

public class MainActivity extends FragmentActivity {    /**     * 钢琴布局     */    private RhythmLayout mRhythmLayout;    /**     * 钢琴布局的适配器     */    private RhythmAdapter mRhythmAdapter;    /**     * 接收PullToRefreshViewPager中的ViewPager控件     */    private ViewPager mViewPager;    /**     * 可以侧拉刷新的ViewPager,其实是一个LinearLayout控件     */    private PullToRefreshViewPager mPullToRefreshViewPager;    /**     * ViewPager的适配器     */    private CardPagerAdapter mPagerAdapter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();    }    private void init() {        //实例化控件        mRhythmLayout = (RhythmLayout) findViewById(R.id.box_rhythm);        mPullToRefreshViewPager = (PullToRefreshViewPager) findViewById(R.id.pager);        //获取PullToRefreshViewPager中的ViewPager控件        mViewPager = mPullToRefreshViewPager.getRefreshableView();        //设置钢琴布局的高度 高度为钢琴布局item的宽度+10dp        int height = (int) mRhythmLayout.getRhythmItemWidth() + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10.0F, getResources().getDisplayMetrics());        mRhythmLayout.getLayoutParams().height = height;        //设置<span style="font-family: Arial;">mPullToRefreshViewPager距离底部的距离为钢琴控件的高        ((RelativeLayout.LayoutParams) this.mPullToRefreshViewPager.getLayoutParams()).bottomMargin = height;        List<Card> cardList = new ArrayList<Card>();        for (int i = 0; i < 30; i++) {            Card card = new Card();            cardList.add(card);        }        //设置ViewPager的适配器        mPagerAdapter = new CardPagerAdapter(getSupportFragmentManager(), cardList);        mViewPager.setAdapter(mPagerAdapter);        //设置钢琴布局的适配器        mRhythmAdapter = new RhythmAdapter(this, cardList);        mRhythmLayout.setAdapter(mRhythmAdapter);    }}

运行后的效果如下:
这里写图片描述


二、钢琴按钮的滚动动画

在写这个动画之前我们需要分析一下这个动画都有哪些步骤
这里写图片描述
观察上面的Gif图,当滑动ViewPager页时,底部的钢琴界面首先会进行一次位移,将ViewPager对应的底部Item移动到中心,之后将此Item升起,将之前的Item降下所以我们总共需要3个动画,一个滚动动画,一个升起动画,一个降下动画,而且在上图中可以看出动画的执行实在ViewPager切换后执行的所以我们需要设置它的OnPageChangeListener。在OnPageSelected方法中执行组合动画

在RhythmLayout中添加方法以供外部调用这个组合动画

/*** 位移到所选中的item位置,并进行相应的动画** @param position 被选中的item位置*/public void showRhythmAtPosition(int position) {    //如果所要移动的位置和上一次一样则退出方法    if (this.mLastDisplayItemPosition == position)        return;    //ScrollView的滚动条位移动画    Animator scrollAnimator;    //item的弹起动画    Animator bounceUpAnimator;    //item的降下动画    Animator shootDownAnimator;    if ((this.mLastDisplayItemPosition < 0) || (mAdapter.getCount() <= 7) || (position <= 3)) {        //当前要位移到的位置为前3个时或者总的item数量小于7个        scrollAnimator = scrollToPosition(0, mScrollStartDelayTime, false);    } else if (mAdapter.getCount() - position <= 3) {        //当前要位移到的位置为最后3个        scrollAnimator = scrollToPosition(mAdapter.getCount() - 7, mScrollStartDelayTime, false);    } else {        //当前位移到的位置既不是前3个也不是后3个        scrollAnimator = scrollToPosition(position - 3, mScrollStartDelayTime, false);    }    //获取对应item升起动画    bounceUpAnimator = bounceUpItem(position, false);    //获取对应item降下动画    shootDownAnimator = shootDownItem(mLastDisplayItemPosition, false);    //动画合集 弹起动画和降下动画的组合    AnimatorSet animatorSet1 = new AnimatorSet();    if (bounceUpAnimator != null) {        animatorSet1.playTogether(bounceUpAnimator);    }    if (shootDownAnimator != null) {        animatorSet1.playTogether(shootDownAnimator);    }    //3个动画的组合    AnimatorSet animatorSet2 = new AnimatorSet();    animatorSet2.playSequentially(new Animator[]{scrollAnimator, animatorSet1});    animatorSet2.start();    mLastDisplayItemPosition = position;}

mLastDisplayItemPosition为上次选中的item的位置,mScrollStartDelayTime为动画延迟执行的时间,其他都有详细的注释,并不难理解scrollToPosition()方法中调用的是AnimatorUtils中的moveScrollViewToX()方法它将会移动ScrollView的x轴到指定的位置

修改后RhythmLayout的代码如下:

public class RhythmLayout extends HorizontalScrollView {    /**     * ScrollView的子控件     */    private LinearLayout mLinearLayout;    /**     * item的宽度,为屏幕的1/7     */    private float mItemWidth;    /**     * 屏幕宽度     */    private int mScreenWidth;    /**     * 当前被选中的的Item的位置     */    private int mCurrentItemPosition;    /**     * 适配器     */    private RhythmAdapter mAdapter;    /**     * item在Y轴位移的单位,以这个值为基础开始阶梯式位移动画     */    private int mIntervalHeight;    /**     * item在Y轴位移最大的高度     */    private int mMaxTranslationHeight;    /**     * 每个图标加上左右2边边距的尺寸     */    private int mEdgeSizeForShiftRhythm;    /**     * 按下屏幕的时间     */    private long mFingerDownTime;    /**     * 上一次所选中的item的位置     */    private int mLastDisplayItemPosition;    /**     * ScrollView滚动动画延迟执行的时间     */    private int mScrollStartDelayTime;    private Context mContext;    private Handler mHandler;    private ShiftMonitorTimer mTimer;    public RhythmLayout(Context context) {        this(context, null);    }    public RhythmLayout(Context context, AttributeSet attrs) {        super(context, attrs);        mContext = context;        init();    }    private void init() {        //获得屏幕大小        DisplayMetrics displayMetrics = new DisplayMetrics();        ((Activity) mContext).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);        mScreenWidth = displayMetrics.widthPixels;        //获取Item的宽度,为屏幕的七分之一        mItemWidth = mScreenWidth / 7;        //初始化时将手指当前所在的位置置为-1        mCurrentItemPosition = -1;        mMaxTranslationHeight = (int) mItemWidth;        mIntervalHeight = (mMaxTranslationHeight / 6);        mEdgeSizeForShiftRhythm = getResources().getDimensionPixelSize(R.dimen.rhythm_edge_size_for_shift);        mFingerDownTime = 0;        mHandler = new Handler();        mTimer = new ShiftMonitorTimer();        mTimer.startMonitor();        mLastDisplayItemPosition = -1;        mScrollStartDelayTime = 0;    }    public void setAdapter(RhythmAdapter adapter) {        this.mAdapter = adapter;        //如果获取HorizontalScrollView下的LinearLayout控件        if (mLinearLayout == null) {            mLinearLayout = (LinearLayout) getChildAt(0);        }        //循环获取adapter中的View,设置item的宽度并且add到mLinearLayout中        mAdapter.setItemWidth(mItemWidth);        for (int i = 0; i < this.mAdapter.getCount(); i++) {            mLinearLayout.addView(mAdapter.getView(i, null, null));        }    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_MOVE://移动                mTimer.monitorTouchPosition(ev.getX(), ev.getY());                updateItemHeight(ev.getX());                break;            case MotionEvent.ACTION_DOWN://按下                mTimer.monitorTouchPosition(ev.getX(), ev.getY());                //得到按下时的时间戳                mFingerDownTime = System.currentTimeMillis();                updateItemHeight(ev.getX());                break;            case MotionEvent.ACTION_UP://抬起                actionUp();                break;        }        return true;    }    //更新钢琴按钮的高度    private void updateItemHeight(float scrollX) {        //得到屏幕上可见的7个钢琴按钮的视图        List viewList = getVisibleViews();        //当前手指所在的item        int position = (int) (scrollX / mItemWidth);        //如果手指位置没有发生变化或者大于childCount的则跳出方法不再继续执行        if (position == mCurrentItemPosition || position >= mLinearLayout.getChildCount())            return;        mCurrentItemPosition = position;        makeItems(position, viewList);    }    /**     * 得到当前可见的7个钢琴按钮     */    private List<View> getVisibleViews() {        ArrayList arrayList = new ArrayList();        if (mLinearLayout == null)            return arrayList;        //当前可见的第一个钢琴按钮的位置        int firstPosition = getFirstVisibleItemPosition();        //当前可见的最后一个钢琴按钮的位置        int lastPosition = firstPosition + 7;        if (mLinearLayout.getChildCount() < 7) {            lastPosition = mLinearLayout.getChildCount();        }        //取出当前可见的7个钢琴按钮        for (int i = firstPosition; i < lastPosition; i++)            arrayList.add(mLinearLayout.getChildAt(i));        return arrayList;    }    /**     * 获得firstPosition-1 和 lastPosition +1 在当前可见的7个总共9个钢琴按钮     *     * @param isForward  是否获取firstPosition - 1 位置的钢琴按钮     * @param isBackward 是否获取lastPosition + 1 位置的钢琴按钮     * @return     */    private List<View> getVisibleViews(boolean isForward, boolean isBackward) {        ArrayList viewList = new ArrayList();        if (this.mLinearLayout == null)            return viewList;        int firstPosition = getFirstVisibleItemPosition();        int lastPosition = firstPosition + 7;        if (mLinearLayout.getChildCount() < 7) {            lastPosition = mLinearLayout.getChildCount();        }        if ((isForward) && (firstPosition > 0))            firstPosition--;        if ((isBackward) && (lastPosition < mLinearLayout.getChildCount()))            lastPosition++;        for (int i = firstPosition; i < lastPosition; i++)            viewList.add(mLinearLayout.getChildAt(i));        return viewList;    }    /**     * 得到可见的第一个钢琴按钮的位置     */    public int getFirstVisibleItemPosition() {        if (mLinearLayout == null) {            return 0;        }        //获取钢琴按钮的数量        int size = mLinearLayout.getChildCount();        for (int i = 0; i < size; i++) {            View view = mLinearLayout.getChildAt(i);            //当出现钢琴按钮的x轴比当前ScrollView的x轴大时,这个钢琴按钮就是当前可见的第一个            if (getScrollX() < view.getX() + mItemWidth / 2.0F)                return i;        }        return 0;    }    /**     * 计算出个个钢琴按钮需要的高度并开始动画     */    private void makeItems(int fingerPosition, List<View> viewList) {        if (fingerPosition >= viewList.size()) {            return;        }        int size = viewList.size();        for (int i = 0; i < size; i++) {            //根据钢琴按钮的位置计算出在Y轴需要位移的大小            int translationY = Math.min(Math.max(Math.abs(fingerPosition - i) * mIntervalHeight, 10), mMaxTranslationHeight);            //位移动画            updateItemHeightAnimator(viewList.get(i), translationY);        }    }    /**     * 根据给定的值进行Y轴位移的动画     *     * @param view     * @param translationY     */    private void updateItemHeightAnimator(View view, int translationY) {        if (view != null)            AnimatorUtils.showUpAndDownBounce(view, translationY, 180, true, true);    }    /**     * 手指抬起时将其他钢琴按钮落下,重置到初始位置     */    private void actionUp() {        mTimer.monitorTouchPosition(-1.0F, -1.0F);        if (mCurrentItemPosition < 0) {            return;        }        int firstPosition = getFirstVisibleItemPosition();        int lastPosition = firstPosition + mCurrentItemPosition;        final List viewList = getVisibleViews();        int size = viewList.size();        //将当前钢琴按钮从要落下的ViewList中删除        if (size > mCurrentItemPosition) {            viewList.remove(mCurrentItemPosition);        }        if (firstPosition - 1 >= 0) {            viewList.add(mLinearLayout.getChildAt(firstPosition - 1));        }        if (lastPosition + 1 <= mLinearLayout.getChildCount()) {            viewList.add(mLinearLayout.getChildAt(lastPosition + 1));        }        //200毫秒后执行动画        this.mHandler.postDelayed(new Runnable() {            public void run() {                for (int i = 0; i < viewList.size(); i++) {                    View downView = (View) viewList.get(i);                    shootDownItem(downView, true);                }            }        }, 200L);        mCurrentItemPosition = -1;        //使设备震动        vibrate(20L);    }    /**     * 位移到Y轴'最低'的动画     *     * @param view    需要执行动画的视图     * @param isStart 是否开始动画     * @return     */    public Animator shootDownItem(View view, boolean isStart) {        if (view != null)            return AnimatorUtils.showUpAndDownBounce(view, mMaxTranslationHeight, 350, isStart, true);        return null;    }    /**     * 位移到Y轴'最低'的动画     *     * @param viewPosition view的位置     * @param isStart      是否开始动画     * @return     */    public Animator shootDownItem(int viewPosition, boolean isStart) {        if ((viewPosition >= 0) && (mLinearLayout != null) && (mLinearLayout.getChildCount() > viewPosition))            return shootDownItem(mLinearLayout.getChildAt(viewPosition), isStart);        return null;    }    /**     * @param position   要移动到的view的位置     * @param duration   动画持续时间     * @param startDelay 延迟动画开始时间     * @param isStart    动画是否开始     * @return     */    public Animator scrollToPosition(int position, int duration, int startDelay, boolean isStart) {        int viewX = (int) mLinearLayout.getChildAt(position).getX();        return smoothScrollX(viewX, duration, startDelay, isStart);    }    /**     * ScrollView滚动动画X轴位移     *     * @param position   view的位置     * @param startDelay 延迟动画开始时间     * @param isStart    动画是否开始     * @return     */    public Animator scrollToPosition(int position, int startDelay, boolean isStart) {        int viewX = (int) mLinearLayout.getChildAt(position).getX();        return smoothScrollX(viewX, 300, startDelay, isStart);    }    private Animator smoothScrollX(int position, int duration, int startDelay, boolean isStart) {        return AnimatorUtils.moveScrollViewToX(this, position, duration, startDelay, isStart);    }    /**     * 位移到Y轴'最高'的动画     *     * @param viewPosition view的位置     * @param isStart      是否开始动画     * @return     */    public Animator bounceUpItem(int viewPosition, boolean isStart) {        if (viewPosition >= 0)            return bounceUpItem(mLinearLayout.getChildAt(viewPosition), isStart);        return null;    }    public Animator bounceUpItem(View view, boolean isStart) {        if (view != null)            return AnimatorUtils.showUpAndDownBounce(view, 10, 350, isStart, true);        return null;    }    /**     * 让移动设备震动     *     * @param l 震动的时间     */    private void vibrate(long l) {        ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE)).vibrate(new long[]{0L, l}, -1);    }    /**     * 计时器,实现爬楼梯效果     */    class ShiftMonitorTimer extends Timer {        private TimerTask timerTask;        /**         *         */        private boolean canShift = false;        private float x;        private float y;        void monitorTouchPosition(float x, float y) {            this.x = x;            this.y = y;            //当按下位置在第一个后最后一个,或x<0,y<0时,canShift为false,使计时器线程中的代码不能执行            if ((x < 0.0F) || ((x > mEdgeSizeForShiftRhythm) && (x < mScreenWidth - mEdgeSizeForShiftRhythm)) || (y < 0.0F)) {                mFingerDownTime = System.currentTimeMillis();                canShift = false;            } else {                canShift = true;            }        }        void startMonitor() {            if (this.timerTask == null) {                timerTask = new TimerTask() {                    @Override                    public void run() {                        long duration = System.currentTimeMillis() - mFingerDownTime;                        //按下时间大于1秒,且按下的是第一个或者最后一个等式成立                        if (canShift && duration > 1000) {                            int firstPosition = getFirstVisibleItemPosition();                            int toPosition = 0; //要移动到的钢琴按钮的位置                            boolean isForward = false; //是否获取第firstPosition-1个钢琴按钮                            boolean isBackward = false;//是否获取第lastPosition+1个钢琴按钮                            final List<View> localList;                            if (x <= mEdgeSizeForShiftRhythm && x >= 0.0F) {//第一个                                if (firstPosition - 1 >= 0) {                                    mCurrentItemPosition = 0;                                    toPosition = firstPosition - 1;                                    isForward = true;                                    isBackward = false;                                }                            } else if (x > mScreenWidth - mEdgeSizeForShiftRhythm) {//最后一个                                if (mLinearLayout.getChildCount() >= 1 + (firstPosition + 7)) {                                    mCurrentItemPosition = 7;                                    toPosition = firstPosition + 1;                                    isForward = false;                                    isBackward = true;                                }                            }                            //当按下的是第一个的时候isForward为true,最后一个时isBackward为true                            if (isForward || isBackward) {                                localList = getVisibleViews(isForward, isBackward);                                final int finalToPosition = toPosition;                                mHandler.post(new Runnable() {                                    public void run() {                                        makeItems(mCurrentItemPosition, localList);//设置每个Item的高度                                        scrollToPosition(finalToPosition, 200, 0, true);//设置ScrollView在x轴的坐标                                        vibrate(10L);                                    }                                });                            }                        }                    }                };            }            //200毫秒之后开始执行,每隔250毫秒执行一次            schedule(timerTask, 200L, 250L);        }    }    /**     * 位移到所选中的item位置,并进行相应的动画     *     * @param position 前往的item位置     */    public void showRhythmAtPosition(int position) {        //如果所要移动的位置和上一次一样则退出方法        if (this.mLastDisplayItemPosition == position)            return;        //ScrollView的滚动条位移动画        Animator scrollAnimator;        //item的弹起动画        Animator bounceUpAnimator;        //item的降下动画        Animator shootDownAnimator;        if ((this.mLastDisplayItemPosition < 0) || (mAdapter.getCount() <= 7) || (position <= 3)) {            //当前要位移到的位置为前3个时或者总的item数量小于7个            scrollAnimator = scrollToPosition(0, mScrollStartDelayTime, false);        } else if (mAdapter.getCount() - position <= 3) {            //当前要位移到的位置为最后3个            scrollAnimator = scrollToPosition(mAdapter.getCount() - 7, mScrollStartDelayTime, false);        } else {            //当前位移到的位置既不是前3个也不是后3个            scrollAnimator = scrollToPosition(position - 3, mScrollStartDelayTime, false);        }        //获取对应item升起动画        bounceUpAnimator = bounceUpItem(position, false);        //获取对应item降下动画        shootDownAnimator = shootDownItem(mLastDisplayItemPosition, false);        //动画合集 弹起动画和降下动画的组合        AnimatorSet animatorSet1 = new AnimatorSet();        if (bounceUpAnimator != null) {            animatorSet1.playTogether(bounceUpAnimator);        }        if (shootDownAnimator != null) {            animatorSet1.playTogether(shootDownAnimator);        }        //3个动画的组合        AnimatorSet animatorSet2 = new AnimatorSet();        animatorSet2.playSequentially(new Animator[]{scrollAnimator, animatorSet1});        animatorSet2.start();        mLastDisplayItemPosition = position;    }    /*     * 得到每个键冒(控件)的宽度     */    public float getRhythmItemWidth() {        return mItemWidth;    }    /**     * 设置滚动动画延迟执行时间     *     * @param scrollStartDelayTime 延迟时间毫秒为单位     */    public void setScrollRhythmStartDelayTime(int scrollStartDelayTime) {        this.mScrollStartDelayTime = scrollStartDelayTime;    }}

AnimatorUtils的代码如下:

public class AnimatorUtils {    /**     * @param view                需要设置动画的view     * @param translationY        偏移量     * @param animatorTime        动画时间     * @param isStartAnimator     是否开启指示器     * @param isStartInterpolator 是否开始动画     * @return 平移动画     */    public static Animator showUpAndDownBounce(View view, int translationY, int animatorTime, boolean isStartAnimator, boolean isStartInterpolator) {        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "translationY", translationY);        if (isStartInterpolator) {            objectAnimator.setInterpolator(new OvershootInterpolator());        }        objectAnimator.setDuration(animatorTime);        if (isStartAnimator) {            objectAnimator.start();        }        return objectAnimator;    }    /**     * 移动ScrollView的x轴     * @param view 要移动的ScrollView     * @param toX  要移动到的X轴坐标     * @param time 动画持续时间     * @param delayTime 延迟开始动画的时间     * @param isStart 是否开始动画     * @return     */    public static Animator moveScrollViewToX(View view, int toX, int time, int delayTime, boolean isStart) {        ObjectAnimator objectAnimator = ObjectAnimator.ofInt(view, "scrollX", new int[]{toX});        objectAnimator.setDuration(time);        objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());        objectAnimator.setStartDelay(delayTime);        if (isStart)            objectAnimator.start();        return objectAnimator;    }}

接下来修改MainActivity中的代码

public class MainActivity extends FragmentActivity {    /**     * 钢琴布局     */    private RhythmLayout mRhythmLayout;    /**     * 钢琴布局的适配器     */    private RhythmAdapter mRhythmAdapter;    /**     * 接收PullToRefreshViewPager中的ViewPager控件     */    private ViewPager mViewPager;    /**     * 可以侧拉刷新的ViewPager,其实是一个LinearLayout控件     */    private PullToRefreshViewPager mPullToRefreshViewPager;    /**     * ViewPager的适配器     */    private CardPagerAdapter mPagerAdapter;    private ViewPager.OnPageChangeListener onPageChangeListener = new ViewPager.OnPageChangeListener() {        @Override        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {        }        @Override        public void onPageSelected(int position) {            mRhythmLayout.showRhythmAtPosition(position);        }        @Override        public void onPageScrollStateChanged(int state) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();    }    private void init() {        //实例化控件        mRhythmLayout = (RhythmLayout) findViewById(R.id.box_rhythm);        mPullToRefreshViewPager = (PullToRefreshViewPager) findViewById(R.id.pager);        //获取PullToRefreshViewPager中的ViewPager控件        mViewPager = mPullToRefreshViewPager.getRefreshableView();        //设置钢琴布局的高度 高度为钢琴布局item的宽度+10dp        int height = (int) mRhythmLayout.getRhythmItemWidth() + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10.0F, getResources().getDisplayMetrics());        mRhythmLayout.getLayoutParams().height = height;        ((RelativeLayout.LayoutParams) this.mPullToRefreshViewPager.getLayoutParams()).bottomMargin = height;        List<Card> cardList = new ArrayList<Card>();        for (int i = 0; i < 30; i++) {            Card card = new Card();            cardList.add(card);        }        //设置ViewPager的适配器        mPagerAdapter = new CardPagerAdapter(getSupportFragmentManager(), cardList);        mViewPager.setAdapter(mPagerAdapter);        //设置钢琴布局的适配器        mRhythmAdapter = new RhythmAdapter(this, cardList);        mRhythmLayout.setAdapter(mRhythmAdapter);        //设置ViewPager的滚动速度        setViewPagerScrollSpeed(this.mViewPager, 400);        //设置控件的监听        mViewPager.setOnPageChangeListener(onPageChangeListener);        //设置ScrollView滚动动画延迟执行的时间        mRhythmLayout.setScrollRhythmStartDelayTime(400);        //初始化时将第一个键帽弹出        mRhythmLayout.showRhythmAtPosition(0);    }    /**     * 设置ViewPager的滚动速度,即每个选项卡的切换速度     * @param viewPager ViewPager控件     * @param speed     滚动速度,毫秒为单位     */    private void setViewPagerScrollSpeed(ViewPager viewPager, int speed) {        try {            Field field = ViewPager.class.getDeclaredField("mScroller");            field.setAccessible(true);            ViewPagerScroller viewPagerScroller = new ViewPagerScroller(viewPager.getContext(), new OvershootInterpolator(0.6F));            field.set(viewPager, viewPagerScroller);            viewPagerScroller.setDuration(speed);        } catch (NoSuchFieldException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }}

值得注意的是第78行调用的setViewPagerScrollSpeed方法,这个方法的作用就是放缓ViewPager切换页的速度,否则太快的切换速度会出现不和谐的感觉,这个方法中使用的ViewPagerScroller的代码如下:

public class ViewPagerScroller extends Scroller {    private int mDuration;    public ViewPagerScroller(Context context) {        super(context);    }    public ViewPagerScroller(Context context, Interpolator interpolator) {        super(context, interpolator);    }    public ViewPagerScroller(Context context, Interpolator interpolator, boolean flywheel) {        super(context, interpolator, flywheel);    }    public void setDuration(int duration) {        this.mDuration = duration;    }    public void startScroll(int startX, int startY, int dx, int dy) {        super.startScroll(startX, startY, dx, dy, this.mDuration);    }}

运行后可以看到切换ViewPager的页卡时底部的钢琴控件就会执行相应的动画,已经将钢琴控件’绑’在了ViewPager上了,但是点击底部的钢琴控件,却并没有执行相应的动画效果,也就是说:可以通过ViewPager控制钢琴控件,但是不能通过钢琴控件控制ViewPager,想要实现这样的相互联系就需要我们在钢琴控件RhythmLayout中监听手指抬起的动作。创建一个抽象接口如下:

public abstract interface IRhythmItemListener {    public abstract void onSelected(int position);}

在RhythmLayout中添加这个监听的成员变量以及一个set方法,如下代码:

/*** 监听器,监听手指离开屏幕时的位置*/private IRhythmItemListener mListener;/*** 设置监听器*/public void setRhythmListener(IRhythmItemListener listener) {    mListener = listener;}

监听手指抬起的动作只要把触发监听放在actionUp()方法中就可以了,修改actionUp()方法

/*** 手指抬起时将其他钢琴按钮落下,重置到初始位置*/private void actionUp() {    mTimer.monitorTouchPosition(-1.0F, -1.0F);    if (mCurrentItemPosition < 0) {        return;    }    int firstPosition = getFirstVisibleItemPosition();    int lastPosition = firstPosition + mCurrentItemPosition;    final List viewList = getVisibleViews();    int size = viewList.size();    //将当前钢琴按钮从要落下的ViewList中删除    if (size > mCurrentItemPosition) {        viewList.remove(mCurrentItemPosition);    }    if (firstPosition - 1 >= 0) {        viewList.add(mLinearLayout.getChildAt(firstPosition - 1));    }    if (lastPosition + 1 <= mLinearLayout.getChildCount()) {        viewList.add(mLinearLayout.getChildAt(lastPosition + 1));    }    //200毫秒后执行动画    this.mHandler.postDelayed(new Runnable() {        public void run() {            for (int i = 0; i < viewList.size(); i++) {                View downView = (View) viewList.get(i);                shootDownItem(downView, true);            }        }    }, 200L);    //触发监听    if (mListener != null)        mListener.onSelected(lastPosition);    mCurrentItemPosition = -1;    //使设备震动    vibrate(20L);}

在里仅仅是在33行和34行添加了2段代码,来触发监听,最后我们只要在MainActivity中调用setRhythmListener这个监听器就做好了,修改后的MainActivity如下:

public class MainActivity extends FragmentActivity {    /**     * 钢琴布局     */    private RhythmLayout mRhythmLayout;    /**     * 钢琴布局的适配器     */    private RhythmAdapter mRhythmAdapter;    /**     * 接收PullToRefreshViewPager中的ViewPager控件     */    private ViewPager mViewPager;    /**     * 可以侧拉刷新的ViewPager,其实是一个LinearLayout控件     */    private PullToRefreshViewPager mPullToRefreshViewPager;    /**     * ViewPager的适配器     */    private CardPagerAdapter mPagerAdapter;    private IRhythmItemListener iRhythmItemListener = new IRhythmItemListener() {        @Override        public void onSelected(final int position) {            new Handler().postDelayed(new Runnable() {                public void run() {                    mViewPager.setCurrentItem(position);                }            }, 100L);        }    };    private ViewPager.OnPageChangeListener onPageChangeListener = new ViewPager.OnPageChangeListener() {        @Override        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {        }        @Override        public void onPageSelected(int position) {            mRhythmLayout.showRhythmAtPosition(position);        }        @Override        public void onPageScrollStateChanged(int state) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();    }    private void init() {        //实例化控件        mRhythmLayout = (RhythmLayout) findViewById(R.id.box_rhythm);        mPullToRefreshViewPager = (PullToRefreshViewPager) findViewById(R.id.pager);        //获取PullToRefreshViewPager中的ViewPager控件        mViewPager = mPullToRefreshViewPager.getRefreshableView();        //设置钢琴布局的高度 高度为钢琴布局item的宽度+10dp        int height = (int) mRhythmLayout.getRhythmItemWidth() + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10.0F, getResources().getDisplayMetrics());        mRhythmLayout.getLayoutParams().height = height;        ((RelativeLayout.LayoutParams) this.mPullToRefreshViewPager.getLayoutParams()).bottomMargin = height;        List<Card> cardList = new ArrayList<Card>();        for (int i = 0; i < 30; i++) {            Card card = new Card();            cardList.add(card);        }        //设置ViewPager的适配器        mPagerAdapter = new CardPagerAdapter(getSupportFragmentManager(), cardList);        mViewPager.setAdapter(mPagerAdapter);        //设置钢琴布局的适配器        mRhythmAdapter = new RhythmAdapter(this, cardList);        mRhythmLayout.setAdapter(mRhythmAdapter);        //设置ViewPager的滚动速度        setViewPagerScrollSpeed(this.mViewPager, 400);        //设置控件的监听        mRhythmLayout.setRhythmListener(iRhythmItemListener);        mViewPager.setOnPageChangeListener(onPageChangeListener);        //设置ScrollView滚动动画延迟执行的时间        mRhythmLayout.setScrollRhythmStartDelayTime(400);        //初始化时将第一个键帽弹出        mRhythmLayout.showRhythmAtPosition(0);    }    /**     * 设置ViewPager的滚动速度,即每个选项卡的切换速度     * @param viewPager ViewPager控件     * @param speed     滚动速度,毫秒为单位     */    private void setViewPagerScrollSpeed(ViewPager viewPager, int speed) {        try {            Field field = ViewPager.class.getDeclaredField("mScroller");            field.setAccessible(true);            ViewPagerScroller viewPagerScroller = new ViewPagerScroller(viewPager.getContext(), new OvershootInterpolator(0.6F));            field.set(viewPager, viewPagerScroller);            viewPagerScroller.setDuration(speed);        } catch (NoSuchFieldException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }}

运行后的效果如下:
这里写图片描述
至此RhythmLayou这个自定义控件我们已经完成了,最后的任务就是背景颜色的转换


三、背景颜色的转换

因为每个选项卡的颜色都是不一样的所以我们需要在我们的数据源Card这个类中添加一个背景颜色的属性

public class Card implements Serializable {    private static final long serialVersionUID = -5376313495678563362L;    private int backgroundColor;    public int getBackgroundColor() {        return backgroundColor;    }    public void setBackgroundColor(int backgroundColor) {        this.backgroundColor = backgroundColor;    }}

在MainActivity的init()方法的添加数据的for循环中设置不同的颜色

for (int i = 0; i < 30; i++) {   Card card = new Card();   //随机生成颜色值   card.setBackgroundColor((int) -(Math.random() * (16777216 - 1) + 1));   mCardList.add(card);}

为了让背景颜色转换的更加和谐我们需要一个过渡动画在AnimationUtils中添加如下方法

/*** 将View的背景颜色更改,使背景颜色转换更和谐的过渡动画* @param view   要改变背景颜色的View* @param preColor  上个颜色值* @param currColor 当前颜色值* @param duration  动画持续时间*/public static void showBackgroundColorAnimation(View view, int preColor, int currColor, int duration) {    ObjectAnimator objectAnimator = ObjectAnimator.ofInt(view, "backgroundColor", new int[]{preColor, currColor});    objectAnimator.setDuration(duration);    objectAnimator.setEvaluator(new ArgbEvaluator());    objectAnimator.start();}

之后只要在初始化的时候设置背景颜色,然后在切换ViewPager的页卡时执行动画就ok了,修改后的MainActivity代码如下

public class MainActivity extends FragmentActivity {    /**     * 钢琴布局     */    private RhythmLayout mRhythmLayout;    /**     * 钢琴布局的适配器     */    private RhythmAdapter mRhythmAdapter;    /**     * 接收PullToRefreshViewPager中的ViewPager控件     */    private ViewPager mViewPager;    /**     * 可以侧拉刷新的ViewPager,其实是一个LinearLayout控件     */    private PullToRefreshViewPager mPullToRefreshViewPager;    /**     * ViewPager的适配器     */    private CardPagerAdapter mPagerAdapter;    /**     * 最外层的View,为了设置背景颜色而使用     */    private View mMainView;    private List<Card> mCardList;    /**     * 记录上一个选项卡的颜色值     */    private int mPreColor;    private IRhythmItemListener iRhythmItemListener = new IRhythmItemListener() {        @Override        public void onSelected(final int position) {            new Handler().postDelayed(new Runnable() {                public void run() {                    mViewPager.setCurrentItem(position);                }            }, 100L);        }    };    private ViewPager.OnPageChangeListener onPageChangeListener = new ViewPager.OnPageChangeListener() {        @Override        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {        }        @Override        public void onPageSelected(int position) {            int currColor = mCardList.get(position).getBackgroundColor();            AnimatorUtils.showBackgroundColorAnimation(mMainView, mPreColor, currColor, 400);            mPreColor = currColor;            mMainView.setBackgroundColor(mCardList.get(position).getBackgroundColor());            mRhythmLayout.showRhythmAtPosition(position);        }        @Override        public void onPageScrollStateChanged(int state) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();    }    private void init() {        //实例化控件        mMainView = findViewById(R.id.main_view);        mRhythmLayout = (RhythmLayout) findViewById(R.id.box_rhythm);        mPullToRefreshViewPager = (PullToRefreshViewPager) findViewById(R.id.pager);        //获取PullToRefreshViewPager中的ViewPager控件        mViewPager = mPullToRefreshViewPager.getRefreshableView();        //设置钢琴布局的高度 高度为钢琴布局item的宽度+10dp        int height = (int) mRhythmLayout.getRhythmItemWidth() + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10.0F, getResources().getDisplayMetrics());        mRhythmLayout.getLayoutParams().height = height;        ((RelativeLayout.LayoutParams) this.mPullToRefreshViewPager.getLayoutParams()).bottomMargin = height;        mCardList = new ArrayList<Card>();        for (int i = 0; i < 30; i++) {            Card card = new Card();            //随机生成颜色值            card.setBackgroundColor((int) -(Math.random() * (16777216 - 1) + 1));            mCardList.add(card);        }        //设置ViewPager的适配器        mPagerAdapter = new CardPagerAdapter(getSupportFragmentManager(), mCardList);        mViewPager.setAdapter(mPagerAdapter);        //设置钢琴布局的适配器        mRhythmAdapter = new RhythmAdapter(this, mCardList);        mRhythmLayout.setAdapter(mRhythmAdapter);        //设置ViewPager的滚动速度        setViewPagerScrollSpeed(this.mViewPager, 400);        //设置控件的监听        mRhythmLayout.setRhythmListener(iRhythmItemListener);        mViewPager.setOnPageChangeListener(onPageChangeListener);        //设置ScrollView滚动动画延迟执行的时间        mRhythmLayout.setScrollRhythmStartDelayTime(400);        //初始化时将第一个键帽弹出,并设置背景颜色        mRhythmLayout.showRhythmAtPosition(0);        mPreColor = mCardList.get(0).getBackgroundColor();        mMainView.setBackgroundColor(mPreColor);    }    /**     * 设置ViewPager的滚动速度,即每个选项卡的切换速度     *     * @param viewPager ViewPager控件     * @param speed     滚动速度,毫秒为单位     */    private void setViewPagerScrollSpeed(ViewPager viewPager, int speed) {        try {            Field field = ViewPager.class.getDeclaredField("mScroller");            field.setAccessible(true);            ViewPagerScroller viewPagerScroller = new ViewPagerScroller(viewPager.getContext(), new OvershootInterpolator(0.6F));            field.set(viewPager, viewPagerScroller);            viewPagerScroller.setDuration(speed);        } catch (NoSuchFieldException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }}

到此已经基本仿照了最美应用的界面了,最后运行后的效果如下:
这里写图片描述

项目Github地址

1 0
原创粉丝点击