Android ListView 项目滚动动画(类似“UIKit Dynamics”)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21588188/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
ListView item scroll animation ("UIKit Dynamics" -like)
提问by b_yng
I am attempting to animate the ListView
items when a scroll takes place. More specifically, I am trying to emulate the scroll animations from the iMessage app on iOS 7. I found a similar example online:
我试图ListView
在发生滚动时为项目设置动画。更具体地说,我试图从 iOS 7 上的 iMessage 应用程序中模拟滚动动画。我在网上找到了一个类似的例子:
To clarify, I'm trying to achieve the "fluid" movement effect on the items when the user scrolls, not the animation when a new item is added. I've attempted to modify the Views in my BaseAdapter
and I've looked into the AbsListView
source to see if I could somehow attach an AccelerateInterpolator
somewhere that would adjust the draw coordinates sent to the children Views (if that is even how AbsListView
is designed). I've been unable to make any progress so far.
为了澄清起见,我试图在用户滚动时对项目实现“流体”移动效果,而不是在添加新项目时实现动画。我试图修改我的视图中的视图,BaseAdapter
并查看了AbsListView
源代码,看看我是否可以以某种方式附加一个AccelerateInterpolator
可以调整发送到子视图的绘制坐标的地方(如果这甚至是如何AbsListView
设计的)。到目前为止,我一直无法取得任何进展。
Does anybody have any ideas of how to replicate this behaviour?
有人对如何复制这种行为有任何想法吗?
For the record to help with googling: this is called "UIKit Dynamics"on ios.
为了帮助谷歌搜索的记录:这在ios上称为“UIKit Dynamics”。
How to replicate Messages bouncing bubbles in iOS 7
It is built-in to recent iOS releases. However it's still somewhat hard to use. (2014) This is the post on it everyone copies:widely copied articleSurprisingly, UIKit Dynamics is only available on apple's "collection view", not on apple's "table view" so all the iOS debs are having to convert stuff from table view to "collection view"
它内置于最近的 iOS 版本中。不过使用起来还是有些吃力。(2014) 这是每个人都复制的帖子:广泛复制的文章令人惊讶的是,UIKit Dynamics 仅适用于苹果的“集合视图”,而不适用于苹果的“表格视图”,因此所有 iOS deb 都必须将表格视图中的内容转换为“集合视图”
The library everyone is using as a starting point is BPXLFlowLayout, since that person pretty much cracked copying the feel of the iphone text messages app. In fact, if you were porting it to Android I guess you could use the parameters in there to get the same feel. FYI I noticed in my android fone collection, HTC phones have this effect, on their UI. Hope it helps. Android rocks!
每个人都作为起点使用的库是BPXLFlowLayout,因为那个人几乎破解了复制 iphone 短信应用程序的感觉。事实上,如果你将它移植到 Android,我想你可以使用那里的参数来获得相同的感觉。仅供参考,我在我的 android fone 系列中注意到,HTC 手机在其 UI 上有这种效果。希望能帮助到你。安卓摇滚!
回答by simekadam
This implementation works quite good. There is some flickering though, probably because of altered indices when the adapter add new views to top or bottom..That could be possibly solved by watching for changes in the tree and shifting the indices on the fly..
这个实现效果很好。但是有一些闪烁,可能是因为当适配器将新视图添加到顶部或底部时索引发生了变化。
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;
}
}
回答by Sagar Shah
Try this by putting this in your getView() method Just before returning your convertView:
尝试将其放入 getView() 方法中 就在返回 convertView 之前:
Animation animationY = new TranslateAnimation(0, 0, holder.llParent.getHeight()/4, 0);
animationY.setDuration(1000);
Yourconvertview.startAnimation(animationY);
animationY = null;
Where llParent = RootLayout which consists your Custom Row Item.
其中 llParent = RootLayout 包含您的自定义行项目。
回答by Justin Pollard
I've taken just a few minutes to explore this and it looks like it can be done pretty easily with API 12 and above (hopefully I'm not missing something ...). To get the very basic card effect, all it takes is a couple lines of code at the end of getView() in your Adapter right before you return it to the list. Here's the entire Adapter:
我只花了几分钟来探索这一点,看起来使用 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;
}
}
Note that I'm keeping track of the last position to be loaded (mLastPosition) in order to determine whether to animate the views up from the bottom (if scrolling down) or down from the top (if we're scrolling up).
请注意,我正在跟踪要加载的最后一个位置 (mLastPosition),以确定是从底部向上(如果向下滚动)还是从顶部向下(如果我们向上滚动)为视图设置动画。
The wonderful thing is, you can do so much more by just modifying the initial convertView properties (e.g. convertView.setScaleX(float scale)) and the convertView.animate() chain (e.g. .scaleX(float)).
奇妙的是,您可以通过修改初始 convertView 属性(例如 convertView.setScaleX(float scale))和 convertView.animate() 链(例如 .scaleX(float))来做更多事情。
回答by SDJMcHattie
It's honestly going to be a lot of work and quite mathematically intense, but I would have thought you could make the list item's layouts have padding top and bottom and that you could adjust that padding for each item so that the individual items become more or less spaced out. How you would track by how much and how you would know the speed at which the items are being scrolled, well that would be the hard part.
老实说,这将需要大量工作,并且在数学上非常紧张,但我认为您可以使列表项的布局具有顶部和底部填充,并且您可以调整每个项目的填充,以便单个项目变得或多或少隔开。您将如何跟踪多少以及如何知道项目滚动的速度,这将是困难的部分。
回答by gargAman
Since we do want items to pop every time they appear at the top or bottom of our list, the best place to do it is the getView() method of the adapter:
由于我们确实希望项目每次出现在我们列表的顶部或底部时都弹出,因此最好的地方是适配器的 getView() 方法:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
animatePostHc(position, v);
} else {
animatePreHc(position, v);
}
回答by Dyna
From what I understand what you are looking for is a parallax effect.
据我了解,您正在寻找的是视差效果。
This answeris really complete and I think that can help you a lot.
这个答案真的很完整,我认为这可以帮助你很多。
回答by vedant
Use this library: http://nhaarman.github.io/ListViewAnimations
使用这个库:http: //nhaarman.github.io/ListViewAnimations
It is very awesome. Better than the iOS in atleast it is open source :)
这是非常棒的。至少比 iOS 好,它是开源的 :)