一、可以侧拉刷新加载的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.PullToRefreshViewPagerandroid: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.RhythmLayoutandroid:id="@+id/box_rhythm"android:layout_width="match_parent"android:layout_height="@dimen/rhythm_layout_height"android:layout_alignParentBottom="true"android:scrollbars="none"><LinearLayoutandroid: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;}@Overridepublic 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();}@Overridepublic 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;@Overrideprotected 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的宽度+10dpint 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;//初始化时将手指当前所在的位置置为-1mCurrentItemPosition = -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));}}@Overridepublic 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();//当前手指所在的itemint 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() {@Overridepublic 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为trueif (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() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Overridepublic void onPageSelected(int position) {mRhythmLayout.showRhythmAtPosition(position);}@Overridepublic void onPageScrollStateChanged(int state) {}};@Overrideprotected 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的宽度+10dpint 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() {@Overridepublic void onSelected(final int position) {new Handler().postDelayed(new Runnable() {public void run() {mViewPager.setCurrentItem(position);}}, 100L);}};private ViewPager.OnPageChangeListener onPageChangeListener = new ViewPager.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Overridepublic void onPageSelected(int position) {mRhythmLayout.showRhythmAtPosition(position);}@Overridepublic void onPageScrollStateChanged(int state) {}};@Overrideprotected 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的宽度+10dpint 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() {@Overridepublic void onSelected(final int position) {new Handler().postDelayed(new Runnable() {public void run() {mViewPager.setCurrentItem(position);}}, 100L);}};private ViewPager.OnPageChangeListener onPageChangeListener = new ViewPager.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Overridepublic 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);}@Overridepublic void onPageScrollStateChanged(int state) {}};@Overrideprotected 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的宽度+10dpint 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. 亏损63亿,美图真能“美”到上市?

    PMCAFF(pmcaff.com):最大互联网产品社区,是百度,腾讯,阿里等产品经理的学习交流平台.定期出品深度产品观察,互联产品研究首选. 数说:数字趣说产品,颠覆你的想象.本文由PMCAFF原创 ...

  2. 赴美IPO后,美菜网还能在卖菜行业保持“低调”吗?

    生鲜电商赛道再起风云,美菜网被传拟赴美IPO. 5月11日,据路透社报道,中国生鲜移动电商平台美菜网拟赴美IPO,计划筹资5亿美元.知情人士透露,该公司正在与财务顾问就潜在的IPO事宜进行合作.然而美 ...

  3. 如何使用相芯科技美妆SDK实现美妆(Android)

    1.美妆功能介绍 相芯SDK提供23种内置美妆妆容,如减龄,邻家女孩,欧美等多样妆容风格.支持口红.腮红. 眉毛.眼影. 眼线.睫毛.美瞳.粉底.眼影.高光等15大维度,多种搭配,轻松实现变妆. 2. ...

  4. picsart下载_照片美易art_照片美易art官方下载_照片美易art官方正版苹果版_好趣手游网...

    照片美易art软件是一款免费的照片编辑应用,照片美易art app不仅有超萌逗趣的贴纸还有超快的海报模版,并且照片美易art还针对照片人物提供各种美化编辑功能. 基本简介 无水印照片秒变海报,画中画美 ...

  5. android 记录美剧观看进度,[推荐]i看美剧应用:美剧播出、新闻发生提醒直接推送到手机...

    原标题:[推荐]i看美剧应用:美剧播出.新闻发生提醒直接推送到手机 [快速导航] 您可以直接把本文直接拉到最低部,点击原文链接,下载IOS版或安卓版. 下载应用的IOS版也可以直接在苹果APP STO ...

  6. 行业大咖到访众美集团 共话众美定制广场十大价值点

    行业大咖到访众美集团 共话众美定制广场十大价值点 9月6日上午,河北省不动产商会常务副会长.石家庄市房地产业协会党支部书记李水源,石家庄市房地产业协会秘书长纪珊珊携石家庄楼市全媒体观察团大V代表走进众 ...

  7. “她经济”作祟医美,美呗如何变美?

    爱美是人之天性,不仅是爱外物的美,人们更爱美的自己. 在"颜值即正义"的时代里,消费者对颜值经济的崛起也都见怪不怪,而颜值经济下,不仅仅是外在美妆行业成为规模庞大的红利市场,能够改 ...

  8. 2023美赛思路 | 2023美赛C题Matlab代码

    2023美赛思路 | 2023美赛C题Matlab代码 目录 2023美赛思路 | 2023美赛C题Matlab代码 基本介绍 程序设计 运行结果 参考资料 基本介绍 (1)问: 本文分两个小问,第一 ...

  9. 医美主流新双美模式,开启医美行业新未来

    如果一家美容院想转型医美,如何申请医疗资质?如何打造符合医疗体系的门店环境?如何掌握医疗体系的经营能力?更重要的是,有多少会员的美容院,可以养活一家可持续发展的医美机构?再加上消费观念的转变和互联网产 ...

最新文章

  1. 让你的网站提速:图片优化网站推荐
  2. Core Java(一)
  3. C语言函数题- 删除字符串中下标为i的字符
  4. oracle中有类似split的方法么,Oracle 实现拆分列数据的split()方法
  5. 自适应 幻灯片代码 app_字节跳动 To B 再添一员,将推出飞书文档独立App | 36氪独家...
  6. SAP 导出 HTML,【我sap这导出数据表格export.mhtml怎么转换为 excel 工作表.xlsx】excel生成html表格数据...
  7. LAMP下http跳转到 https
  8. VMware Workstation不可恢复错误: (vcpu-0)
  9. Windows Azure 配置Active Directory 主机(1)
  10. python经纬度转换xy坐标公式_Python经纬度坐标转换为距离及角度的实现
  11. 使用pascal voc训练测试faster rcnn
  12. UVa 10827 - Maximum sum on a torus
  13. DASCTFNepCTF 部分writeup
  14. 浅析Python文件操作
  15. 怎么在Ubuntu手机上发送短信及拨打电话
  16. Acorn Mac 7.1.1
  17. 2口kvm切换器使用方法简述
  18. barrier()函数
  19. 南邮 | 汇编实验 3.17:用户键入“通行字”,显示欢迎界面
  20. portstats matlab,MATLAB金融计算试题

热门文章

  1. UVM中Objection的作用
  2. java打印倒空三角形_用猴子打印《莎士比亚全集》的办法解决字节跳动水壶题...
  3. 网络安全之信息收集技术(全)
  4. [sdm660 android9.0]GPIO的使用配置方法
  5. [SDM660 Android9.0]音频模块:xs2002的使能与调试
  6. Learning2Rank 学习
  7. 宗教信仰和推荐系统解决同一问题
  8. 基于行为心理学的网络购物推荐算法思考
  9. linux硬盘对拷慢,解决NTFS拷贝文件远比磁盘物理读取速度慢的问题
  10. Parallel用法