仿最美应用-每日最美 钢琴律动效果(二)
来源:互联网 发布: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地址
- 仿最美应用-每日最美 钢琴律动效果(二)
- 仿最美应用-每日最美 钢琴律动效果(一)
- 仿最美应用-每日最美 钢琴律动效果(一)
- 最美应用--收音机
- 仿iPhone《最美应用》app的底部滑动菜单效果
- 仿照最美物语点赞效果
- 每日一学(二)刮刮卡效果实现
- 世界上最贵的钢琴
- 挥洒力与美的律动 ——张治国书法印象
- 钢琴
- 最美应用-从Android研发工程师的角度之[最美时光]
- 最美应用-从Android研发工程师的角度之[最美时光]
- 最美应用-从Android研发工程师的角度之[最美时光]
- 最美
- 最美
- 编程之美 最短摘要解法二
- 【无限互联】学员作品:最美应用iOS客户端
- 《你最美》换发型应用项目源码
- Hibernate的Lazy加载策略
- [ZJOI2012]网络 解题报告
- CCF201403(1,2)
- 第五周项目1(3)
- Unity3D工程师需要掌握的技能
- 仿最美应用-每日最美 钢琴律动效果(二)
- myBatis学习笔记(二)
- Linux Web服务器网站故障分析常用的命令
- Problem Description
- 教程: Windows 7、Vista 开启ipv6支持
- LeetCode89. Gray Code
- 在VC++6.0中调通官网上opencv和MFC完美结合的经典例程需要注意到几点问题
- BZOJ_P1935 [Shoi2007]Tree 园丁的烦恼(离散化+树状数组+差分思想)
- [面试] C/C++ —— MFC(二)