为早期版本的Android添加工具栏阴影/高程

43

我将我的Android应用程序更新为新的材料设计,但我还想给工具栏添加一些阴影或高程。似乎可以通过图像/9-patches的某些(hacky)方式实现,但我想知道是否可以通过支持库来完成。(就像CardView可以有高程一样)

根据这个关于另一个问题的答案,这可以通过将Toolbar包装在AppBarLayout中来实现,但对我不起作用。

我的布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">
    <android.support.v7.widget.Toolbar
            android:id="@+id/Toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>

我也尝试通过XML和代码设置高度,但都无效。

任何帮助都将不胜感激!提前致谢。

更新:

由于我在其他布局中包含了我的工具栏布局,因此以下是我的主要布局之一:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <include
        layout="@layout/Toolbar" />
    <fragment
        class="OverAllField.XamarinAndroid.Fragments.Planning.PlanningFragment"
        android:id="@+id/PlanningFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>

你是否在使用<include>标签将其包含在你的活动布局中?如果是,请发布你的活动布局。 - Binoy Babu
@BinoyBabu 是的,我使用include标签。我已经更新了我的答案,并附上了活动布局。感谢您的时间。 - avb
请参考我的答案..https://dev59.com/AV8d5IYBdhLWcg3wiSjI#30962710 - Ranjithkumar
8个回答

69
针对 Android 5.0 及以上版本:AppBarLayout 在布局中自动提供/添加阴影。您还可以通过 app:elevation="4dp" 增加 AppBarLayout 的高度。
针对 Pre-Lollipop 版本:您可以使用以下链接:https://github.com/vipulasri/Toolbar-Elevation-Pre-Lollipop 注意:Toolbar 也支持使用 android:elevation="4dp" 来设置高度。
新更新:Appcompat v24.0.0 中,你不能再使用 setElevation()app:elevation 来设置 AppBarLayout 的高程,因为这些已经被弃用。
现在,你必须使用 stateListAnimator 属性来设置高程。
注意: 在 StateListAnimator 中将持续时间设置为 1ms,以便避免高程绘制的延迟

AppBarLayout elevation change is delayed on appCompat v24.0.0

res 目录下的 animator-v21 文件夹中的 appbar_always_elevated.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
    <objectAnimator android:propertyName="elevation"
                    android:valueTo="8dp" 
                    android:valueType="floatType"
                    android:duration="1"/>
</item>
</selector>

在 AppbarLayout 中:
<android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:fitsSystemWindows="true"
        android:stateListAnimator="@animator/appbar_always_elevated"
        android:theme="@style/AppTheme.AppBarOverlay">

</android.support.design.widget.AppBarLayout>

@oli.G 在这种情况下,你必须使用第二个选项。 - Vipul Asri
@VipulAsri,链接(http://blog.grafixartist.com/add-a-toolbar-elevation-on-pre-lollipop/)已失效。 - iRuth
2
如果您将此应用于具有列表的活动工具栏,则链接并不是非常有用。当您开始向下滚动时,“阴影”会完全隐藏项目,并在您“超滚”到顶部时完全取代了突出显示颜色。不好。 - Aspiring Dev
谢谢,stateListAnimator 在21以上版本完美运作。如果想以编程方式使用它,请执行以下操作:appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(context, R.animator.appbar_always_elevated)); - Mohamed
@Trancer,那是你的错误,工具栏和阴影不应该共享同一个父级,我也遇到了这个问题。 - David
显示剩余3条评论

7
尝试在活动布局中使用AppBarLayout。尝试:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <include
            layout="@layout/Toolbar" />
    </android.support.design.widget.AppBarLayout>

    <fragment
        class="OverAllField.XamarinAndroid.Fragments.Planning.PlanningFragment"
        android:id="@+id/PlanningFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>

Toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/Toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

这在我的情况下也没有显示阴影。我正在使用安卓4.4.4 KitKat的手机,如果这有帮助的话。谢谢 - avb
那我就不知道了。我认为你可能需要在早期版本的片段前景中使用投影阴影。 - Binoy Babu
好的,很奇怪它在你那里可以工作。也许这与我使用Xamarin有关,它只能在本地支持库中工作...那我会看看其他解决方案的。无论如何,感谢你的帮助,非常感激。 - avb
@avb1994 我也有同样的问题,而且我也在使用 Xamarin。所以可能是 Xamarin 的问题。 :-( - Daisy

2
我有一个带有工具栏和类似工具栏的视图的CollapsingToolbarLayout,可以向上和向下移动,但位于NestedScrollView之上,就像在https://github.com/k0shk0sh/CoordinatorLayoutExample中一样。 我尝试了许多变体。有时候阴影会在带有NestedScrollView的屏幕上滚动,有时候工具栏会绘制不透明的实心阴影,有时候阴影会被锯齿化。无论如何,这是我的解决方案。
假设您有一个布局:
<android.support.design.widget.CoordinatorLayout>
    <android.support.design.widget.AppBarLayout>
         <android.support.design.widget.CollapsingToolbarLayout>
             <android.support.v7.widget.Toolbar>
                 <!-- Toolbar views if needed -->
             </android.support.v7.widget.Toolbar>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <!-- Footer Toolbar views if needed -->

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout>
            <!-- Views -->
        </LinearLayout>

    </android.support.v4.widget.NestedScrollView>

    <!-- Add this view. It draws a shadow and aligns top of NestedScrollView -->
    <!-- Set visibility to gone or visible -->
    <View
        android:id="@+id/scroll_shadow"
        android:layout_width="match_parent"
        android:layout_height="4dp"
        android:background="@drawable/shadow"
        android:visibility="gone"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>

添加一个阴影 (drawable/shadow.xml):
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient
        android:angle="90"
        android:endColor="#ffcccccc"
        android:startColor="#00cccccc" />
</shape>

添加这个方法。其中,scrollShadow是名为“scroll_shadow”的视图:
private void setShadowVisibility(int visibility) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        scrollShadow.setVisibility(visibility);
    }
}

随心所欲地操纵它。它将在Android 5.0以下的设备上显示渐变,并在Android 5.0+上显示普通阴影。

1
尝试了这个解决方案,适用于RecyclerViewNestedScrollView,对于静止和移动的Toolbar都适用。 - CoolMind

2

使用构建文件:


compile 'com.android.support:cardview-v7:23.1.1'

请参考此链接

在xml中调用,需添加:

app:cardElevation="8dp"
app:cardCornerRadius="8dp"
app:contentPadding="5dp"

2
我认为最好的解决方案是在工具栏下方放置一个渐变阴影视图,并根据设备SDK调整可见性。

toolbar.xml

 <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@android:color/white"
            android:fitsSystemWindows="true"
            app:popupTheme="@style/AppTheme.PopupOverlay">

            <TextView
                android:id="@+id/centerTitleToolbarTextView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:maxLines="1"
                android:textColor="@color/color_toolbar"
                android:textSize="@dimen/titleToolbar" />

        </android.support.v7.widget.Toolbar>

        <View
            android:id="@+id/shadow_view"
            android:layout_width="match_parent"
            android:visibility="gone"
            android:layout_height="4dp"
            android:background="@drawable/toolbar_shadow" />

    </LinearLayout>

toolbar_shadow.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient
        android:startColor="#c1c1c1"
        android:centerColor="#e6e6e6"
        android:endColor="#f1f1f1"
        android:angle="270" />
</shape>

MainActivity.class

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        findViewById(R.id.shadow_view).setVisibility(View.VISIBLE);
      }

1

我也因这个问题浪费了一天的时间,但最终我找到了解决方法。

/**
 * Controller for the toolbar which will take care of the drop down shadow also on pre-lollipop devices.
 * <br/>
 * The controller also handles the status bar based on the state of the toolbar.
 * <p/>
 * Created by <b>Negru Ionut Valentin</b> on <b>20/1/2016</b>.
 */
public class ToolbarController {

    private boolean handleStatusBar = false;
    private boolean showTitle = true;

    /**
     * Call this method in onCreate() method of your Activity or in onCreateView() in Fragment
     *
     * @param view
     *         The view into which to look for the toolbar
     * @param activity
     *         The activity for which setup the toolbar
     *
     * @return The toolbar found and customized or {@code null}
     */
    public Toolbar initToolbar(View view, Activity activity) {
        Toolbar mToolbar = (Toolbar) view.findViewById(R.id.toolbar);

        // Valid and visible toolbar - otherwise ignore
        if (null != mToolbar && mToolbar.getVisibility() == View.VISIBLE) {

            int paddingLeft = mToolbar.getPaddingLeft();
            int paddingRight = mToolbar.getPaddingRight();
            int paddingBottom = mToolbar.getPaddingBottom();
            // Set the top padding of the toolbar to match the status bar height
            int paddingTop = new CynnyContextWrapper(activity).getStatusBarHeight();

            mToolbar.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);

            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
                ViewParent parent = mToolbar.getParent();
                if (parent instanceof RelativeLayout) {
                    // Manually create the drop shadow
                    RelativeLayout.LayoutParams params =
                            new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                                                            Metrics.convertDpToPixel(4, activity));

                    View dropShadow = new View(activity);
                    dropShadow.setBackgroundResource(R.drawable.toolbar_shadow);

                    params.addRule(RelativeLayout.BELOW, R.id.toolbar);

                    ((RelativeLayout) parent).addView(dropShadow, params);
                }
            }

            if (activity instanceof AppCompatActivity) {
                // Check if the Activity actually support ActionBar with Toolbar and set our custom Toolbar for it
                ((AppCompatActivity) activity).setSupportActionBar(mToolbar);

                // Get the actionbar from the activity
                ActionBar actionBar = ((AppCompatActivity) activity).getSupportActionBar();
                if (null != actionBar) {
                    // If the actionbar is valid, customize it
                    actionBar.setDisplayHomeAsUpEnabled(true);
                    actionBar.setHomeButtonEnabled(true);

                    actionBar.setDisplayShowTitleEnabled(this.showTitle);

                    mToolbar.setNavigationIcon(R.drawable.ic_arrow_back_selector);
                }
            }

            if (this.handleStatusBar) {
                // For showing the status bar
                activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
                activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
            }
        } else if (handleStatusBar) {
            // Force hide the status bar
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        }

        return mToolbar;
    }

    /**
     * Set the flag indicating if the controller should handle or not the status bar.
     *
     * @param handleStatusBar
     *         Flag which indicates if the initialization of the toolbar should also update
     *         the status bar state (if two calls are made for the init, the last one will
     *         be taken into consideration).
     *
     * @return The current toolbar controller.
     */
    public ToolbarController setHandleStatusBar(boolean handleStatusBar) {
        this.handleStatusBar = handleStatusBar;

        return this;
    }

    /**
     * Set the flag indicating if the toolbar should show or hide the title.
     *
     * @param showTitle
     *         Flag indicating if the toolbar should also show the title (the title can be changed after the toolbar
     *         initialization)
     *
     * @return The current toolbar controller.
     */
    public ToolbarController setShowTitle(boolean showTitle) {
        this.showTitle = showTitle;

        return this;
    }
}

在活动中,您应该像这样在onCreate()方法中使用它:
// Create and customize the Toolbar controller
new ToolbarController().setHandleStatusBar(true).setShowTitle(true)
                                 .initToolbar(((ViewGroup) findViewById(android.R.id.content)).getChildAt(0),
                                              this);

在片段中,您应该像这样在onCreateView()方法中使用它:
new ToolbarController().setHandleStatusBar(false).setShowTitle(false).initToolbar(resultView, getActivity());

请不要忘记在布局中添加工具栏,并使用android:id="@id/toolbar"设置其ID。如果您想使用其他ID,可以自定义控制器并添加另一个setter方法,该方法使用您提供的ID。
我创建了两个工具栏布局: v21
    <!-- This is the new widget added in Lollipop - use with care -->
    <android.support.v7.widget.Toolbar
            android:id="@id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"
            android:theme="@style/TitleToolbarTheme"
            android:background="?android:attr/colorPrimary"
            android:elevation="@dimen/toolbar_elevation"
            />
</merge>

其他

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
        >

    <!-- This is the new widget added in Lollipop - use with care -->
    <android.support.v7.widget.Toolbar
            android:id="@id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"
            android:theme="@style/TitleToolbarTheme"
            android:background="?attr/colorPrimary"
            />

</merge>

我在我的布局中使用它们,使用以下方式:

<include layout="@layout/toolbar" />

我希望这可以帮助解决您的问题。请注意,这可以进一步优化,但对我而言已经有效,我不想再浪费时间在这个问题上了。

在代码示例中,toolbar_shadow是什么? - tasomaniac
这是一个9patch可绘制资源。 - Ionut Negru

1
当使用CoordinatorLayout时,一个简单的解决方案是将ImageView与“阴影”锚定在AppBarLayoutToolbar的正下方。这甚至适用于CollapsingToolbarLayout,并且它正确地渲染在“content”的顶部:
<CoordinatorLayout>
  <AppBarLayout android:id="@+id/app_bar">
    <Toolbar/>
  </AppBarLayout>
  <include layout="@layout/app_bar_shadow"/>
  <!-- your content usually goes here -->
</CoordinatorLayout>

创建一个res/layout/app_bar_shadow.xml文件,该文件仅在Lollipop之前的设备上使用:
<ImageView
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:adjustViewBounds="true"
  app:layout_anchor="@+id/app_bar"
  app:layout_anchorGravity="bottom"
  android:layout_gravity="bottom"
  app:srcCompat="@drawable/shadow_app_bar"
  />

创建一个“空”的res/layout-v21/app_bar_shadow.xml文件,用于Android 5+设备:
 <merge/>

最后,一个适当的res/drawable/shadow_app_bar.xml:
<shape>
  <gradient
    android:angle="90"
    android:startColor="#00000000"
    android:endColor="#30000000"
    />
  <size android:height="@dimen/design_appbar_elevation" />
</shape>

0
使用 CardView。
   <android.support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="top"
    app:cardCornerRadius="0dp">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">


        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:popupTheme="@style/AppTheme.PopupOverlay" />
    </android.support.design.widget.AppBarLayout>
</android.support.v7.widget.CardView>

嗨,Rina,你能解释一下你建议的改变是什么吗?为什么它会解决这个问题? - liorsolomon
将应用栏在早期的Lolipop设备中提升,将其包围在CardView中,并根据需要提升CardView。这将提升应用栏并自动给予阴影。@liorsolomon - rina lakhani
这就像是我见过的最糟糕的解决方案。 - aga

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