ListView 项目滚动动画(类似“UIKit 动态") [英] ListView item scroll animation ("UIKit Dynamics" -like)

查看:21
本文介绍了ListView 项目滚动动画(类似“UIKit 动态")的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在发生滚动时为 ListView 项目设置动画.更具体地说,我试图从 iOS 7 上的 iMessage 应用程序模拟滚动动画.我发现了一个类似的例子

澄清一下,我正在尝试实现流畅"用户滚动时对项目的移动效果,而不是添加新项目时的动画.我试图修改我的 BaseAdapter 中的视图,我查看了 AbsListView 源代码,看看我是否可以以某种方式附加一个 AccelerateInterpolator某处可以调整发送到子视图的绘制坐标(如果这就是 AbsListView 的设计方式).到目前为止,我一直无法取得任何进展.

有人知道如何复制这种行为吗?


为了帮助谷歌搜索的记录:这在 ios 上被称为 UIKit Dynamics".

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

它内置于最近的 iOS 版本中.不过使用起来还是有些吃力.(2014) 这是每个人都复制的帖子:广泛复制文章 出人意料的是,UIKit Dynamics 只在苹果的集合视图"上可用,在苹果的表视图"上不可用所以所有的 iOS deb 都必须将表格视图中的内容转换为集合视图"

每个人都作为起点使用的库是 BPXLFlowLayout,因为那个人几乎完全模仿了 iphone 短信应用程序的感觉.事实上,如果你将它移植到 Android,我猜你可以使用那里的参数来获得相同的感觉.仅供参考,我在我的 android fone 系列中注意到,HTC 手机在其 UI 上有这种效果.希望能帮助到你.Android 摇滚!

解决方案

这个实现效果很好.但是,有一些闪烁,可能是因为当适配器向顶部或底部添加新视图时更改了索引..这可以通过观察树中的变化并动态移动索引来解决..

 public class ElasticListView extends GridView 实现 AbsListView.OnScrollListener, View.OnTouchListener {私有静态 int SCROLLING_UP = 1;私有静态 int SCROLLING_DOWN = 2;私人 int mScrollState;私人 int mScrollDirection;私人 int mTouchedIndex;私人视图 mTouchedView;私人 int mScrollOffset;私人 int mStartScrollOffset;私有布尔 mAnimate;私有 HashMap动画项目;公共 ElasticListView(上下文上下文){超级(上下文);在里面();}公共 ElasticListView(上下文上下文,AttributeSet attrs){超级(上下文,属性);在里面();}公共 ElasticListView(上下文上下文,AttributeSet attrs,int defStyle){超级(上下文,属性,defStyle);在里面();}私有无效初始化(){mScrollState = SCROLL_STATE_IDLE;mScrollDirection = 0;mStartScrollOffset = -1;mTouchedIndex = Integer.MAX_VALUE;mAnimate = true;动画项目 = 新的 HashMap<>();this.setOnTouchListener(this);this.setOnScrollListener(this);}@覆盖public void onScrollStateChanged(AbsListView view, int scrollState) {如果(mScrollState != scrollState){mScrollState = 滚动状态;mAnimate = true;}如果(滚动状态 == SCROLL_STATE_IDLE){mStartScrollOffset = Integer.MAX_VALUE;mAnimate = true;开始动画();}}@覆盖public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {如果(mScrollState == SCROLL_STATE_TOUCH_SCROLL){如果(mStartScrollOffset == Integer.MAX_VALUE){mTouchedView = getChildAt(mTouchedIndex - getPositionForView(getChildAt(0)));如果(mTouchedView == null)返回;mStartScrollOffset = mTouchedView.getTop();} else if (mTouchedView == null) return;mScrollOffset = mTouchedView.getTop() - mStartScrollOffset;int tmpScrollDirection;如果(mScrollOffset > 0){tmpScrollDirection = SCROLLING_UP;} 别的 {tmpScrollDirection = SCROLLING_DOWN;}如果(mScrollDirection != tmpScrollDirection){开始动画();mScrollDirection = tmpScrollDirection;}如果(Math.abs(mScrollOffset)> 200){mAnimate = 假;开始动画();}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));//检查边界if (indexOfLastAnimatedItem >= getChildCount()) {indexOfLastAnimatedItem = getChildCount() - 1;} else if (indexOfLastAnimatedItem <0) {indexOfLastAnimatedItem = 0;}如果(mScrollDirection == SCROLLING_DOWN){setAnimationForScrollingDown(mTouchedIndex - getPositionForView(getChildAt(0)), indexOfLastAnimatedItem, firstVisibleItem);} 别的 {setAnimationForScrollingUp(mTouchedIndex - getPositionForView(getChildAt(0)), indexOfLastAnimatedItem, firstVisibleItem);}如果(Math.abs(mScrollOffset)> 200){mAnimate = 假;开始动画();mTouchedView = null;mScrollDirection = 0;mStartScrollOffset = -1;mTouchedIndex = Integer.MAX_VALUE;mAnimate = true;}}}私人无效开始动画(){for (ViewPropertyAnimator 动画师:animatedItems.values()) {动画师开始();}animationItems.clear();}私有无效 setAnimationForScrollingDown(int indexOfTouchedChild, int indexOflastAnimatedChild, int firstVisibleIndex) {for (int i = indexOfTouchedChild + 1; i <= indexOflastAnimatedChild; i++) {查看 v = getChildAt(i);v.setTranslationY((-1f * mScrollOffset));如果 (!animatedItems.containsKey(v)) {animationItems.put(v, v.animate().translationY(0).setDuration(300).setStartDelay(50 * i));}}}私有无效 setAnimationForScrollingUp(int indexOfTouchedChild, int indexOflastAnimatedChild, int firstVisibleIndex) {for (int i = indexOfTouchedChild - 1; i > 0; i--) {查看 v = getChildAt(i);v.setTranslationY((-1 * mScrollOffset));如果 (!animatedItems.containsKey(v)) {animationItems.put(v, v.animate().translationY(0).setDuration(300).setStartDelay(50 * (indexOfTouchedChild - i)));}}}@覆盖公共布尔 onTouch(查看 v,MotionEvent 事件){开关 (event.getActionMasked()) {案例 MotionEvent.ACTION_DOWN:矩形矩形 = 新矩形();int childCount = getChildCount();int[] listViewCoords = new int[2];getLocationOnScreen(listViewCoords);int x = (int)event.getRawX() - listViewCoords[0];int y = (int)event.getRawY() - listViewCoords[1];查看子项;for (int i = 0; i < childCount; i++) {child = getChildAt(i);child.getHitRect(rect);如果(rect.contains(x,y)){mTouchedIndex = getPositionForView(child);休息;}}返回假;}返回假;}}

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:

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.

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.

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 article Surprisingly, 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"

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!

解决方案

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;

}

}

这篇关于ListView 项目滚动动画(类似“UIKit 动态")的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆