ListView项滚动animation(“UIKit Dynamics”样)

我正在尝试animation滚动发生时的ListView项目。 更具体地说,我试图模拟iOS 7上的iMessage应用程序的滚动animation。我在网上find了一个类似的例子:

为了澄清,我试图在用户滚动时实现对项目的“stream动”移动效果,而不是在添加新项目时的animation。 我试图修改我的BaseAdapter的视图,我已经查看了AbsListView源,看看我是否可以以某种方式附加一个AccelerateInterpolator地方,将调整绘制坐标发送到子视图(如果这甚至是如何AbsListView的devise)。 到目前为止,我一直无法取得进展。

有没有人有任何想法如何复制这种行为?


logging来帮助谷歌search:这就是所谓的“UIKitdynamic”对ios。

如何复制消息在iOS 7中弹跳泡泡

它是内置到最近的iOS版本。 但是使用起来还是有些困难的。 (2014)这是每个人的副本复制: 广泛复制的文章令人惊讶的是,UIKitdynamic只适用于苹果的“集合视图”,而不是在苹果的“表视图”,所以所有的iOS debs不得不从表视图转换的东西“收集视图”

大家正在使用的图书馆是BPXLFlowLayout ,因为那个人几乎破解复制的iPhone文本消息应用程序的感觉。 事实上,如果你将它移植到Android上,我想你可以使用那里的参数来获得相同的感觉。 仅供参考我注意到在我的android fone集合中,HTC手机在他们的用户界面上有这个效果。 希望能帮助到你。 Android的岩石!

这个实现工作很好。 虽然有一些闪烁,可能是因为当适配器添加新的视图顶部或底部时改变了索引。这可能可以通过观察树中的变化和移动索引来解决。

 public class ElasticListView extends GridView implements AbsListView.OnScrollListener, View.OnTouchListener { private static int SCROLLING_UP = 1; private static int SCROLLING_DOWN = 2; private int mScrollState; private int mScrollDirection; private int mTouchedIndex; private View mTouchedView; private int mScrollOffset; private int mStartScrollOffset; private boolean mAnimate; private HashMap<View, ViewPropertyAnimator> animatedItems; public ElasticListView(Context context) { super(context); init(); } public ElasticListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public ElasticListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mScrollState = SCROLL_STATE_IDLE; mScrollDirection = 0; mStartScrollOffset = -1; mTouchedIndex = Integer.MAX_VALUE; mAnimate = true; animatedItems = new HashMap<>(); this.setOnTouchListener(this); this.setOnScrollListener(this); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mScrollState != scrollState) { mScrollState = scrollState; mAnimate = true; } if (scrollState == SCROLL_STATE_IDLE) { mStartScrollOffset = Integer.MAX_VALUE; mAnimate = true; startAnimations(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (mScrollState == SCROLL_STATE_TOUCH_SCROLL) { if (mStartScrollOffset == Integer.MAX_VALUE) { mTouchedView = getChildAt(mTouchedIndex - getPositionForView(getChildAt(0))); if (mTouchedView == null) return; mStartScrollOffset = mTouchedView.getTop(); } else if (mTouchedView == null) return; mScrollOffset = mTouchedView.getTop() - mStartScrollOffset; int tmpScrollDirection; if (mScrollOffset > 0) { tmpScrollDirection = SCROLLING_UP; } else { tmpScrollDirection = SCROLLING_DOWN; } if (mScrollDirection != tmpScrollDirection) { startAnimations(); mScrollDirection = tmpScrollDirection; } if (Math.abs(mScrollOffset) > 200) { mAnimate = false; startAnimations(); } Log.d("test", "direction:" + (mScrollDirection == SCROLLING_UP ? "up" : "down") + ", scrollOffset:" + mScrollOffset + ", toucheId:" + mTouchedIndex + ", fvisible:" + firstVisibleItem + ", " + "visibleItemCount:" + visibleItemCount + ", " + "totalCount:" + totalItemCount); int indexOfLastAnimatedItem = mScrollDirection == SCROLLING_DOWN ? getPositionForView(getChildAt(0)) + getChildCount() : getPositionForView(getChildAt(0)); //check for bounds if (indexOfLastAnimatedItem >= getChildCount()) { indexOfLastAnimatedItem = getChildCount() - 1; } else if (indexOfLastAnimatedItem < 0) { indexOfLastAnimatedItem = 0; } if (mScrollDirection == SCROLLING_DOWN) { setAnimationForScrollingDown(mTouchedIndex - getPositionForView(getChildAt(0)), indexOfLastAnimatedItem, firstVisibleItem); } else { setAnimationForScrollingUp(mTouchedIndex - getPositionForView(getChildAt(0)), indexOfLastAnimatedItem, firstVisibleItem); } if (Math.abs(mScrollOffset) > 200) { mAnimate = false; startAnimations(); mTouchedView = null; mScrollDirection = 0; mStartScrollOffset = -1; mTouchedIndex = Integer.MAX_VALUE; mAnimate = true; } } } private void startAnimations() { for (ViewPropertyAnimator animator : animatedItems.values()) { animator.start(); } animatedItems.clear(); } private void setAnimationForScrollingDown(int indexOfTouchedChild, int indexOflastAnimatedChild, int firstVisibleIndex) { for (int i = indexOfTouchedChild + 1; i <= indexOflastAnimatedChild; i++) { View v = getChildAt(i); v.setTranslationY((-1f * mScrollOffset)); if (!animatedItems.containsKey(v)) { animatedItems.put(v, v.animate().translationY(0).setDuration(300).setStartDelay(50 * i)); } } } private void setAnimationForScrollingUp(int indexOfTouchedChild, int indexOflastAnimatedChild, int firstVisibleIndex) { for (int i = indexOfTouchedChild - 1; i > 0; i--) { View v = getChildAt(i); v.setTranslationY((-1 * mScrollOffset)); if (!animatedItems.containsKey(v)) { animatedItems.put(v, v.animate().translationY(0).setDuration(300).setStartDelay(50 * (indexOfTouchedChild - i))); } } } @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: Rect rect = new Rect(); int childCount = getChildCount(); int[] listViewCoords = new int[2]; getLocationOnScreen(listViewCoords); int x = (int)event.getRawX() - listViewCoords[0]; int y = (int)event.getRawY() - listViewCoords[1]; View child; for (int i = 0; i < childCount; i++) { child = getChildAt(i); child.getHitRect(rect); if (rect.contains(x, y)) { mTouchedIndex = getPositionForView(child); break; } } return false; } return false; } } 

我花了几分钟的时间来探索这个,看起来它可以很容易地使用API​​ 12及以上(希望我不会错过的东西…)。 要获得非常基本的卡片效果,只需在适配器中的getView()的末尾添加几行代码,然后再将其返回到列表中。 这是整个适配器:

  public class MyAdapter extends ArrayAdapter<String>{ private int mLastPosition; public MyAdapter(Context context, ArrayList<String> objects) { super(context, 0, objects); } private class ViewHolder{ public TextView mTextView; } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { holder = new ViewHolder(); convertView = LayoutInflater.from(getContext()).inflate(R.layout.grid_item, parent, false); holder.mTextView = (TextView) convertView.findViewById(R.id.checkbox); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.mTextView.setText(getItem(position)); // This tells the view where to start based on the direction of the scroll. // If the last position to be loaded is <= the current position, we want // the views to start below their ending point (500f further down). // Otherwise, we start above the ending point. float initialTranslation = (mLastPosition <= position ? 500f : -500f); convertView.setTranslationY(initialTranslation); convertView.animate() .setInterpolator(new DecelerateInterpolator(1.0f)) .translationY(0f) .setDuration(300l) .setListener(null); // Keep track of the last position we loaded mLastPosition = position; return convertView; } } 

请注意,我正在跟踪要加载的最后一个位置(mLastPosition),以便确定是从底部(如果向下滚动)还是从顶部向下(如果正在向上滚动)对视图进行animation处理。

奇妙的是,只需修改最初的convertView属性(例如convertView.setScaleX(float scale))和convertView.animate()链(例如.scaleX(float))即可。

在这里输入图像说明

试试这个把它放在你的getView()方法中在返回你的convertView之前:

 Animation animationY = new TranslateAnimation(0, 0, holder.llParent.getHeight()/4, 0); animationY.setDuration(1000); Yourconvertview.startAnimation(animationY); animationY = null; 

其中llParent =由您的自定义行项目组成的RootLayout。

老实说会做很多工作,而且math上非常激烈,但是我可能会认为你可以让列表项的布局具有填充顶部和底部,并且可以调整每个项目的填充以使单个项目变得更多或更less间隔开。 你将如何跟踪多less和你将如何知道项目滚动的速度,这将是困难的一部分。

由于我们希望项目每次出现在列表的顶部或底部时都会popup,因此执行此操作的最佳位置是适配器的getView()方法:

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { animatePostHc(position, v); } else { animatePreHc(position, v); } 

从我所了解的你所寻找的是一种视差效应。

这个答案是非常完整的,我认为这可以帮助你很多。

使用这个库: http : //nhaarman.github.io/ListViewAnimations

演示

这是非常棒的。 在至less它比开放源码的iOS更好:)