ActionBar的Overlay模式如何不遮盖顶部内容的问题

EdgHood 9年前

 关于actionbar的overlay模式请参考 如何让android的actionbar浮动且透明 一文。这篇文章讲的是如何在这种模式下让actionbar不遮住顶部的内容。

这一般是这样的场景,在一个ListView显示图片的界面中,当ListView向下滑动的时候,actionbar是是浮动在GridView上面一层的,但是当ListView滚动到顶部,顶部的内容是完全显示出来的,当然这种情况一般ActionBar我们会做成透明效果。

其实很多人都能想到的是,将ListView加上一个高度和actionbar的高度相同的header不就行了吗?

但是,难点是如何得到actionbar的高度。

actionbar的高度其实是在android系统主题的资源文件中定义的,如果你没有主动去修改actionbar的高度,那么可以通过下面的代码来获取:

TypedArray actionbarSizeTypedArray = getActivity().obtainStyledAttributes(new int[] {          android.R.attr.actionBarSize  });  float h = actionbarSizeTypedArray.getDimension(0, 0);

但是这种方式并不太规范,而且在android4.4之后,statusbar所在的区域也是可以显示内容的,这时你还得去计算statusbar的高度。

其实FrameLayout 中boolean fitSystemWindows(Rect insets)方法的insets参数就包含了非内容区域的高度。fitSystemWindows会在加载的时候被调用,如果我们在ListView重写fitSystemWindows不就可以知道该给ListView添加多高的HeaderView了吗?

但是一般我们不希望这样用ListView,因为使用重写的ListView的几率实在太大了(下拉刷新ListView等),而采取另外的方法,把ListView和一个重写了fitSystemWindows方法的FrameLayout放在同一个FrameLayout中,然后通过回调的方式来通知ListView已经获取到了actionbar(或者+statusbar)的高度了。

我们将这个实现了fitSystemWindows方法的FrameLayout命名为:DrawInsetsFrameLayout

代码如下:

/*   * Copyright 2014 Google Inc.   *   * Licensed under the Apache License, Version 2.0 (the "License");   * you may not use this file except in compliance with the License.   * You may obtain a copy of the License at   *   *     http://www.apache.org/licenses/LICENSE-2.0   *   * Unless required by applicable law or agreed to in writing, software   * distributed under the License is distributed on an "AS IS" BASIS,   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   * See the License for the specific language governing permissions and   * limitations under the License.   */  package com.example.drawinsetsframelayoutdemo;  import android.content.Context;  import android.content.res.TypedArray;  import android.graphics.Canvas;  import android.graphics.Rect;  import android.graphics.drawable.Drawable;  import android.util.AttributeSet;  import android.widget.FrameLayout;  /**   * A layout that draws something in the insets passed to {@link #fitSystemWindows(Rect)}, i.e. the area above UI chrome   * (status and navigation bars, overlay action bars).   */  public class DrawInsetsFrameLayout extends FrameLayout {      private Drawable mInsetBackground;      private Rect mInsets;      private Rect mTempRect = new Rect();      private OnInsetsCallback mOnInsetsCallback;      public DrawInsetsFrameLayout(Context context) {          super(context);          init(context, null, 0);      }      public DrawInsetsFrameLayout(Context context, AttributeSet attrs) {          super(context, attrs);          init(context, attrs, 0);      }      public DrawInsetsFrameLayout(Context context, AttributeSet attrs, int defStyle) {          super(context, attrs, defStyle);          init(context, attrs, defStyle);      }      private void init(Context context, AttributeSet attrs, int defStyle) {          final TypedArray a = context.obtainStyledAttributes(attrs,                  R.styleable.DrawInsetsFrameLayout, defStyle, 0);          if (a == null) {              return;          }          mInsetBackground = a.getDrawable(R.styleable.DrawInsetsFrameLayout_insetBackground);          a.recycle();          setWillNotDraw(true);      }      @Override      protected boolean fitSystemWindows(Rect insets) {          mInsets = new Rect(insets);          setWillNotDraw(mInsetBackground == null);          postInvalidateOnAnimation();          if (mOnInsetsCallback != null) {              mOnInsetsCallback.onInsetsChanged(insets);          }          return true; // consume insets      }      @Override      protected void onDraw(Canvas canvas) {          super.onDraw(canvas);          int width = getWidth();          int height = getHeight();          if (mInsets != null && mInsetBackground != null) {              // Top              mTempRect.set(0, 0, width, mInsets.top);              mInsetBackground.setBounds(mTempRect);              mInsetBackground.draw(canvas);              // Bottom              mTempRect.set(0, height - mInsets.bottom, width, height);              mInsetBackground.setBounds(mTempRect);              mInsetBackground.draw(canvas);              // Left              mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom);              mInsetBackground.setBounds(mTempRect);              mInsetBackground.draw(canvas);              // Right              mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom);              mInsetBackground.setBounds(mTempRect);              mInsetBackground.draw(canvas);          }      }      @Override      protected void onAttachedToWindow() {          super.onAttachedToWindow();          if (mInsetBackground != null) {              mInsetBackground.setCallback(this);          }      }      @Override      protected void onDetachedFromWindow() {          super.onDetachedFromWindow();          if (mInsetBackground != null) {              mInsetBackground.setCallback(null);          }      }      /**       * Allows the calling container to specify a callback for custom processing when insets change (i.e. when       * {@link #fitSystemWindows(Rect)} is called. This is useful for setting padding on UI elements based on       * UI chrome insets (e.g. a Google Map or a ListView). When using with ListView or GridView, remember to set       * clipToPadding to false.       */      public void setOnInsetsCallback(OnInsetsCallback onInsetsCallback) {          mOnInsetsCallback = onInsetsCallback;      }      public static interface OnInsetsCallback {          public void onInsetsChanged(Rect insets);      }  }

其中最主要的就是fitSystemWindows方法,其他的不过是绘制DrawInsetsFrameLayout在actionbar部分的显示颜色而已。

如何使用DrawInsetsFrameLayout呢?

package com.example.drawinsetsframelayoutdemo;  import java.util.ArrayList;  import java.util.HashMap;  import java.util.List;  import java.util.Map;  import android.app.Activity;  import android.graphics.Rect;  import android.graphics.drawable.ColorDrawable;  import android.os.Bundle;  import android.util.Log;  import android.view.Menu;  import android.view.MenuItem;  import android.view.View;  import android.view.Window;  import android.view.ViewGroup.LayoutParams;  import android.widget.AbsListView;  import android.widget.ListView;  import android.widget.SimpleAdapter;  public class MainActivity extends Activity {      private ListView listView;      @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          listView= (ListView) findViewById(R.id.listview);          String[] from = { "Text", "Button" };          int[] to = { R.id.text, R.id.button };          List<Map<String, ?>> list = new ArrayList<Map<String, ?>>();          for (int i = 0; i < 103; i++) {              Map<String, String> m = new HashMap<String, String>();              m.put("Text", "Text" + i);              m.put("Button", "Button" + i);              list.add(m);          }          SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.listitem, from, to);          listView.setAdapter(adapter);                                                                                                                                                                                                                                     DrawInsetsFrameLayout drawInsetsFrameLayout = (DrawInsetsFrameLayout) findViewById(R.id.my_draw_insets_layout);          drawInsetsFrameLayout.setOnInsetsCallback(new DrawInsetsFrameLayout.OnInsetsCallback() {              @Override              public void onInsetsChanged(Rect insets) {                  // Update the map padding (inset the compass, zoom buttons, attribution, etc.)                  Log.i("", "insets.top = " + insets.top);                  View headerView = new View(MainActivity.this);                  AbsListView.LayoutParams params = new AbsListView.LayoutParams(LayoutParams.FILL_PARENT, insets.top);                                                                                                                                                                                                                                                    headerView.setLayoutParams(params);                  headerView.setBackgroundColor(0x33000000);                   listView.addHeaderView(headerView);              }          });      }                                                                                                                                                                                                                    }

设置actionbar风格的xml文件:

<!-- Application theme. -->  <style name="AppTheme" parent="AppBaseTheme">      <item name="android:actionBarStyle">@style/TranslucentActionBar</item>      <item name="android:windowActionBarOverlay">true</item>      <item name="android:windowTranslucentStatus">true</item>  </style>  <style name="TranslucentActionBar" parent="android:Widget.Holo.Light.ActionBar.Solid.Inverse">      <item name="android:background">@null</item>  </style>

其中TranslucentActionBar是为DrawInsetsFrameLayout自定义的一个属性,而activity的actionbar和statusbar在这里我们都是设置成了浮动模式的,注意<item name="android:windowTranslucentStatus">true</item>

最后是布局代码:

<?xml version="1.0" encoding="utf-8"?>  <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:yourapp="http://schemas.android.com/apk/res-auto"      android:layout_width="fill_parent"      android:layout_height="fill_parent"      >      <ListView          android:id="@+id/listview"          android:layout_width="match_parent"          android:layout_height="match_parent"      />      <com.example.drawinsetsframelayoutdemo.DrawInsetsFrameLayout          android:id="@+id/my_draw_insets_layout"          android:layout_width="match_parent"          android:layout_height="match_parent"          yourapp:insetBackground="#9000" />  </FrameLayout>

效果图:

-----


完整的demo代码在这里下载:http://download.csdn.net/detail/jianghejie123/7992853