当前位置: 首页 > 知识库问答 >
问题:

列表视图项滚动动画(“UIKit Dynamics” -like)

吕子真
2023-03-14

当发生滚动时,我试图将< code>ListView项目动画化。更具体地说,我正试图模仿iOS 7上iMessage应用程序的滚动动画。我在网上找到一个类似的例子:

为了澄清,我试图在用户滚动时实现对项目的“流体”移动效果,而不是在添加新项目时实现动画。我试图修改我的BaseAdapter中的视图,并且我已经研究了AbsListView源代码,看看我是否可以以某种方式在某个地方附加一个加速插值器,该器可以调整发送到子视图的绘制坐标(如果这是AbsListView的设计方式)。到目前为止,我一直无法取得任何进展。

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

为了记录在案,以帮助谷歌搜索:这在ios上被称为“UIKit Dynamics”。

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

它内置于最近的iOS版本中。但是,它仍然有点难以使用。(2014)这是每个人都复制的帖子:广泛复制的文章 令人惊讶的是,UIKit Dynamics仅在Apple的“集合视图”上可用,而不是在Apple的“表视图”上可用,因此所有iOS debs都必须将内容从表视图转换为“集合视图”。

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

共有3个答案

孟征
2023-03-14

在返回convertView之前,将其放入getView()方法中:

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

其中 llParent = RootLayout,它由自定义行项组成。

刘修能
2023-03-14

我只花了几分钟来探索这个问题,看起来使用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(浮点缩放))和convertViews,您就可以做更多的事情。动画()链(例如,scaleX(float))。

莘俊能
2023-03-14

这个实现非常有效。不过也有一些闪烁,可能是因为适配器在顶部或底部添加新视图时改变了索引。这可以通过观察树中的变化并动态移动索引来解决。。

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;

}

}
 类似资料:
  • 我有一个android布局,它有一个,里面有很多元素。在的底部有一个,然后由适配器填充。 我遇到的问题是,android将从中排除,因为已经具有可滚动的功能。我希望与内容一样长,并且主滚动视图是可滚动的。 我怎样才能达到这种行为呢? 下面是我的主要布局: 然后以编程方式将组件添加到具有ID:的linearlayour中。下面是加载到LinearLayout中的一个视图。就是这个给我卷轴带来麻烦的。

  • 我的TableView包含一些数字数据。当我说按按钮编辑值时,它会将单元格的背景更改为绿色。当没有足够的行使表格可滚动时,这种方法很有效。一旦表格变成(或从一开始就是)可滚动的,它就会开始表现出怪异的行为。它会更改已编辑项目的背景,但也会更改在向下滚动之前不可见的项目的背景。然而,当我调试时,更改背景的代码只被调用一次。我认为它与tableview有关,当is scrollable破坏并重新创建单

  • 我试图实现某种排序工具栏-但没有工具栏(而不是工具栏,它将是一个下拉元素,它本质上是一个RelativeLayout,其正下方有LinearLayout(下拉列表中的扩展项目),一旦按下下拉菜单,它就会在整个布局上悬停。 我考虑过实现折叠工具栏并将下拉列表放入工具栏中,但这可能不是一个好主意,因为下拉列表不是像图像视图这样的静态组件。我还在我的应用程序中使用了ActionBar,因此迁移到工具栏很

  • 当前为 5.4 版本,稍后将升级到 5.5。在 5.5 中,本节内容被拆分成了多个小节。 使用动画视图 在 Unity 中,动画视图用于预览和编辑游戏对象的动画剪辑。动画视图可以通过菜单 Window -> Animation 打开。 查看游戏对象上的动画 动画视图和层级视图、场景视图以及检视视图紧密耦合。类似于检视视图,动画视图将显示当前选中对象的动画的时间轴和关键帧。你也可以在层级视图或场景视

  • 我一直在研究一个思想,在这个思想中,它保持自动滚动,而不需要用户交互,这一点在使用android API(例如,SmoothScrollTopositionFromTop)时是绝对可行的。 我需要提高流畅度,效率和控制速度

  • 我正在设计一个具有滚动视图的页面,其上方是表格视图(禁用滚动)。为此,我在这个问题中提到了答案 - 使UITableView不可滚动并调整高度以容纳所有单元格,但没有成功。 视图层次结构以及提供的约束- -主视图 -滚动视图< br >固定在主视图的所有边上(0,0,0,0),限制边距 -内容视图 固定到滚动视图(0,0,0,0),与主视图宽度相等,与主视图高度相等(优先级-250) -内容视图中