安卓下拉框展开动画材料设计

22

我只需要一些相对简单的东西。在网上找不到任何指南/资源来帮助我实现以下行为

所以有三件事:

  1. 出现在视图中的涟漪
  2. 弹出窗口的缩放动画(找不到自定义的方法)
  3. 文本从Spinner字段传递到实际弹出窗口

任何帮助都将不胜感激。

编辑

涟漪实际上是可以在文档中找到的直接的东西。弹出窗口下拉列表的缩放动画是我最感兴趣的部分。如果我只能获得对该弹出窗口的引用,我就可以按照自己的意愿进行动画处理……有什么想法吗?


你能详细说明一下你所说的“2(放大动画)”是什么意思吗?我在视频中没有看到。 - serv-inc
下拉菜单的弹出窗口会从触摸点位置(或编辑文本视图位置,取决于您的观察方式)逐渐放大。 - Ivelius
2个回答

4
"I've been working on a solution to this for over 48 hours now (I have some extra time, don't blame me) and I can confirm the following:
1. If you use the AppCompat theme and the regular Spinner, you get ripples for free (yay!). If you have a custom Spinner, make sure to extend AppCompatSpinner instead, and you'll get some good theming. According to the AppCompatSpinner javadoc: A Spinner which supports compatible features on older versions of the platform, including:
- Allows dynamic tint of its background via the background tint methods in ViewCompat. - Allows setting of the background tint using backgroundTint and backgroundTintMode. - Allows setting of the popup's theme using popupTheme.
This will automatically be used when you use Spinner in your layouts. You should only need to manually use this class when writing custom views."
  1. There is no way to retrieve the PopupWindow that displays by extending ANY class. There are a lot of nested classes that contribute to the feature (spoiler: They are mostly private).

  2. You can customize the enter and exit transitions of the PopupWindow...statically in your theme (woohoo!). I'm currently using AppCompat v23 and doing some investigation i found:

    • The style that customizes ListPopupWindow LOLLIPOP and above

      <style name="Widget.Material.ListPopupWindow">
          <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
          <item name="popupBackground">@drawable/popup_background_material</item>
          <item name="popupElevation">@dimen/floating_window_z</item>
          <item name="popupAnimationStyle">@empty</item>
          <item name="popupEnterTransition">@transition/popup_window_enter</item>
          <item name="popupExitTransition">@transition/popup_window_exit</item>
          <item name="dropDownVerticalOffset">0dip</item>
          <item name="dropDownHorizontalOffset">0dip</item>
          <item name="dropDownWidth">wrap_content</item>
      </style>
      
    • The style that customizes it pre-LOLLIPOP:

      <style name="Base.Widget.AppCompat.ListPopupWindow" parent="">
          <item name="android:dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
          <item name="android:popupBackground">@drawable/abc_popup_background_mtrl_mult</item>
          <item name="android:dropDownVerticalOffset">0dip</item>
          <item name="android:dropDownHorizontalOffset">0dip</item>
          <item name="android:dropDownWidth">wrap_content</item>
      </style>
      
    • And this is what you set in your Theme:

      // From the constructor listPopupWindowStyle is the 
      // theme attribute you're looking for.
      public ListPopupWindow(Context context, AttributeSet attrs) {
          this(context, attrs, R.attr.listPopupWindowStyle);
      }
      
虽然如此,我仍在获取您的详细信息的过程中,但我通过克隆2个类来解决它: AppCompatSpinner和ListPopupWindow,并向克隆的ListPopupWindow代码中添加一个公共的getPopup() getter,使其可以在我的克隆AppCompatSpinner中访问。这打开了几个机会。一个例子是我的centerPopup()方法,它应基于spinnerItem(显示在spinner视图本身中的选定项)和spinnerDropdownItem(作为下拉列表中的列表项显示的单个视图)的中心移动布局弹出窗口的位置。然后,在调用super.show()之后,DropdownPopup.show()中调用centerPopup()方法。
private void centerPopup(boolean updateListSelection) {
    boolean aboveAnchor = getPopup().isAboveAnchor();

    int[] spinnerLocation = new int[2];
    int[] popupLocation = new int[2];

    ListView listView = getListView();

    /*
        Popup is anchored at spinner TOP LEFT when it is set to overlap else at BOTTOM LEFT

        Also seems the windowlayoutparams generated for popup is wrapcontent for width and height
        As a result popup locationOnScreen returns [0, 0] which is relative to the window.
        We can take it as relative to spinner location and add spinnerLocation X value to give
        left edge of popup
     */

    MaterialSpinner.this.getLocationInWindow(spinnerLocation);
    int spMidX = spinnerLocation[0] + (MaterialSpinner.this.getWidth() / 2);
    int spMidY = spinnerLocation[1] + (MaterialSpinner.this.getHeight() / 2);

    Rect spinnerBgdPadding = new Rect();
    MaterialSpinner.this.getBackground().getPadding(spinnerBgdPadding);

    // ----- BUG - returns erroneous height
    // Ideally should measure one of the drop down list children and give a height
    // exactly as it wouldwhen laid out eventually.
    // Works only for lists with homogenously tall content
    View child = listView.getAdapter().getView(0, null, listView);

    child.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));

    child.measure(
            MeasureSpec.makeMeasureSpec(listView.getWidth() - listView.getPaddingLeft() - listView.getPaddingRight(), MeasureSpec.AT_MOST),
            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
    );
    int listItemHeight = child.getMeasuredHeight();
    // ----- /BUG

    getPopup().getContentView().getLocationInWindow(popupLocation);
    int popMidX = spinnerLocation[0] + (getPopup().getWidth()) / 2;
    int popMidY = spinnerLocation[1] + (listItemHeight / 2);

    int hoff = getHorizontalOffset();
    int voff = getVerticalOffset();

    int xoff = spMidX - popMidX - hoff - spinnerBgdPadding.left;
    int yoff = spMidY - popMidY - voff;

    getPopup().update(spinnerLocation[0] + xoff, spinnerLocation[1] + yoff, -1, -1, true);

    if (updateListSelection)
        listView.setSelectionFromTop(MaterialSpinner.this.getSelectedItemPosition(), 0)
    ;

    // getPopup().update(MaterialSpinner.this, xoff, yoff, -1, -1);
    // int numVisItems = listView.getLastVisiblePosition() - getFirstVisiblePosition();

}

如果正确的listItemHeight被返回,弹出窗口不仅应该出现在下拉列表上,而且文本居中也应该对齐。(可能由于背景和填充相关问题而偏移了一些像素,但它们可以解决)。
一旦居中,下一步就是将dropDownList selectedPosition的View滚动到popupWindow中心并相应地更新yoff Y偏移量。 popupEnterTransition属性限制为只有LOLLIPOP,但如果这不是问题,您可以在那里插入缩放动画,这样就可以获得所需的效果。但我还没有尝试过,但我认为更好的动画效果应该是从下拉列表中心开始的reveal效果(从你的规格视频看起来是这样的)。
但所有这些都很好……理论上x_x哈哈

1
对你所做的工作表示尊重。已点赞。我仍然认为通过ViewTreeObserver检索弹出窗口是可能的,即使它非常hacky。http://developer.android.com/reference/android/view/ViewTreeObserver.OnGlobalLayoutListener.html 。顺便说一句,你正在进行自定义,尽管它们有点微小。实际上,我最终实现了自己的弹出系统,它的行为方式符合我的要求。但是很遗憾,谷歌没有一个直接的方法来实现他们自己的指南。 - Ivelius
是的,我知道这是自定义的,但我认为它比从头开始创建自己的弹出系统更简洁。话虽如此,即使我还没有探索其他方法,如果我个人在做这件事情,这就是我最初会做的,然后如果这不够用(备好食物和能量饮料和x_x),再尝试自己实现。另外,我不知道你如何使用ViewTreeObserver.OnGlobalLayoutListener来获取弹出窗口。 - efemoney
理论上,使用ViewTreeObserver,您可以尝试在窗口层次结构中识别新视图,并聪明地假设(或预测)新视图是您的弹出窗口。在理论上... - Ivelius
所以让我猜一下:在这种情况下,你会在点击下拉框时将View树观察者监听器注册到Window上。它会捕获刚添加到窗口的弹出窗口,然后你可以取消注册vto?我在想这个是因为我相信弹出窗口是在它自己的窗口中创建的。 - efemoney
真的:D 那只是一个理论假设。 - Ivelius

0

1) 实现水波纹效果有两种方法:一种是将CardView作为每个单独列表项的父元素,另一种是创建一个选择器并将其设置为单个列表项的背景。

2) 您可以尝试使用元素转换来相应地动画化您的视图

3) 您应该将您的文本作为额外数据从购物信息列表项传递到内部下拉列表,或者您需要简单地保存和维护所选位置

您可能还想使用第三方库来实现此功能。 请访问http://android-arsenal.com 查找一个!


理论上,你是正确的。实际上,我可以全部自定义。问题在于是否使用内置的Spinner小部件。 - Ivelius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接