web-dev-qa-db-ja.com

ListViewアイテムのスクロールアニメーション(「UIKit Dynamics」のような)

スクロールが発生したときに、ListViewアイテムをアニメーション化しようとしています。具体的には、iOS 7のiMessageアプリからスクロールアニメーションをエミュレートしようとしています。同様の例 online が見つかりました。

明確にするために、新しいアイテムが追加されたときのアニメーションではなく、ユーザーがスクロールするときにアイテムに「流体」移動効果を達成しようとしています。 BaseAdapterのビューを変更しようとし、AbsListViewソースを調べて、描画を調整するAccelerateInterpolatorを何らかの方法で添付できるかどうかを確認しました。子ビューに送信される座標(AbsListViewが設計されている場合でも)。私はこれまで何も進歩できませんでした。

誰もこの行動を再現する方法についてのアイデアを持っていますか?


グーグルを支援するレコードの場合:これは、iosでは "UIKit Dynamics"と呼ばれます。

iOS 7でバブルをバウンスするメッセージを複製する方法

最新のiOSリリースに組み込まれています。ただし、使用するのはやや困難です。 (2014)これはみんなのコピーです: 広くコピーされた記事 驚くべきことに、UIKit DynamicsはAppleの「コレクションビュー」でのみ利用でき、Appleの「テーブルビュー」では利用できないため、すべてのiOSのデブはテーブルビューから「コレクションビュー」にデータを変換する

誰もが出発点として使用しているライブラリはBPXLFlowLayoutです。なぜなら、その人はiphoneテキストメッセージアプリの感触をコピーするのをかなりクラックしたからです。実際、Androidに移植する場合は、そこのパラメーターを使用して同じ感覚を得ることができると思います。参考までに、私のAndroid foneコレクションで、HTC電話はUIにこの効果を持っています。それが役に立てば幸い。 Android動揺!

62
b_yng

この実装は非常にうまく機能します。ただし、おそらくアダプターが上部または下部に新しいビューを追加するときにインデックスが変更されたために、いくつかのちらつきがあります。

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;

}

}
17
simekadam

ConvertViewを返す直前に、これをgetView()メソッドに入れて、これを試してください。

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

LlParent =カスタム行アイテムを構成するRootLayout。

16
Sagar Shah

これを調査するのにほんの数分しかかかりませんでしたが、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)を追跡していることに注意してください。

すばらしいことは、最初のconvertViewプロパティ(例:convertView.setScaleX(float scale))とconvertView.animate()チェーン(例:.scaleX(float))を変更するだけで、さらに多くのことができるということです。

enter image description here

16
Justin Pollard

正直なところ、多くの作業が必要であり、数学的には非常に集中していますが、リストアイテムのレイアウトに上下のパディングを作成し、各アイテムのパディングを調整して個々のアイテムを多かれ少なかれにすることができると思います間隔が空いています。アイテムをスクロールする速度をどの程度、どのように知るかをどのように追跡するか、それは難しい部分です。

1
SDJMcHattie

リストの最上部または最下部に表示されるたびにアイテムをポップする必要があるため、それを行うのに最適な場所は、アダプターのgetView()メソッドです。

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