FrameworkActionBar.java revision 476e582d2ffdf25102d4c55f8c242baa3d21d37f
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.layoutlib.bridge.bars; 18 19import com.android.ide.common.rendering.api.RenderResources; 20import com.android.ide.common.rendering.api.ResourceValue; 21import com.android.ide.common.rendering.api.SessionParams; 22import com.android.internal.R; 23import com.android.internal.view.menu.MenuBuilder; 24import com.android.internal.view.menu.MenuItemImpl; 25import com.android.layoutlib.bridge.android.BridgeContext; 26import com.android.layoutlib.bridge.impl.ResourceHelper; 27 28import android.annotation.NonNull; 29import android.annotation.Nullable; 30import android.content.Context; 31import android.content.res.TypedArray; 32import android.util.DisplayMetrics; 33import android.util.TypedValue; 34import android.view.InflateException; 35import android.view.View; 36import android.view.View.MeasureSpec; 37import android.view.ViewGroup; 38import android.view.ViewGroup.LayoutParams; 39import android.widget.ActionMenuPresenter; 40import android.widget.FrameLayout; 41import android.widget.ListAdapter; 42import android.widget.ListView; 43import android.widget.RelativeLayout; 44 45import java.util.ArrayList; 46 47/** 48 * Creates the ActionBar as done by the framework. 49 */ 50public class FrameworkActionBar extends BridgeActionBar { 51 52 private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout"; 53 54 // The Action Bar 55 @NonNull private FrameworkActionBarWrapper mActionBar; 56 57 // A fake parent for measuring views. 58 @Nullable private ViewGroup mMeasureParent; 59 60 /** 61 * Inflate the action bar and attach it to {@code parentView} 62 */ 63 public FrameworkActionBar(@NonNull BridgeContext context, @NonNull SessionParams params, 64 @NonNull ViewGroup parentView) { 65 super(context, params, parentView); 66 67 View decorContent = getDecorContent(); 68 69 mActionBar = FrameworkActionBarWrapper.getActionBarWrapper(context, getCallBack(), 70 decorContent); 71 72 FrameLayout contentRoot = (FrameLayout) mEnclosingLayout.findViewById(android.R.id.content); 73 74 // If something went wrong and we were not able to initialize the content root, 75 // just add a frame layout inside this and return. 76 if (contentRoot == null) { 77 contentRoot = new FrameLayout(context); 78 setMatchParent(contentRoot); 79 mEnclosingLayout.addView(contentRoot); 80 setContentRoot(contentRoot); 81 } else { 82 setContentRoot(contentRoot); 83 setupActionBar(); 84 mActionBar.inflateMenus(); 85 } 86 } 87 88 @Override 89 protected ResourceValue getLayoutResource(BridgeContext context) { 90 ResourceValue layoutName = 91 context.getRenderResources().findItemInTheme(LAYOUT_ATTR_NAME, true); 92 if (layoutName != null) { 93 // We may need to resolve the reference obtained. 94 layoutName = context.getRenderResources().findResValue(layoutName.getValue(), 95 layoutName.isFramework()); 96 } 97 if (layoutName == null) { 98 throw new InflateException("Unable to find action bar layout (" + LAYOUT_ATTR_NAME 99 + ") in the current theme."); 100 } 101 return layoutName; 102 } 103 104 @Override 105 protected void setupActionBar() { 106 super.setupActionBar(); 107 mActionBar.setupActionBar(); 108 } 109 110 @Override 111 protected void setHomeAsUp(boolean homeAsUp) { 112 mActionBar.setHomeAsUp(homeAsUp); 113 } 114 115 @Override 116 protected void setTitle(CharSequence title) { 117 mActionBar.setTitle(title); 118 } 119 120 @Override 121 protected void setSubtitle(CharSequence subtitle) { 122 mActionBar.setSubTitle(subtitle); 123 } 124 125 @Override 126 protected void setIcon(String icon) { 127 mActionBar.setIcon(icon); 128 } 129 130 /** 131 * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to 132 * the content frame which shall serve as the new content root. 133 */ 134 @Override 135 public void createMenuPopup() { 136 if (!isOverflowPopupNeeded()) { 137 return; 138 } 139 140 DisplayMetrics metrics = mBridgeContext.getMetrics(); 141 MenuBuilder menu = mActionBar.getMenuBuilder(); 142 OverflowMenuAdapter adapter = new OverflowMenuAdapter(menu, mActionBar.getPopupContext()); 143 144 ListView listView = new ListView(mActionBar.getPopupContext(), null, 145 R.attr.dropDownListViewStyle); 146 RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( 147 measureContentWidth(adapter), LayoutParams.WRAP_CONTENT); 148 layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END); 149 if (mActionBar.isSplit()) { 150 layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); 151 layoutParams.bottomMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin(); 152 } else { 153 layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); 154 layoutParams.topMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin(); 155 } 156 layoutParams.setMarginEnd(getPixelValue("5dp", metrics)); 157 listView.setLayoutParams(layoutParams); 158 listView.setAdapter(adapter); 159 final TypedArray a = mActionBar.getPopupContext().obtainStyledAttributes(null, 160 R.styleable.PopupWindow, R.attr.popupMenuStyle, 0); 161 listView.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground)); 162 listView.setDivider(a.getDrawable(R.attr.actionBarDivider)); 163 a.recycle(); 164 listView.setElevation(mActionBar.getMenuPopupElevation()); 165 mEnclosingLayout.addView(listView); 166 } 167 168 private boolean isOverflowPopupNeeded() { 169 boolean needed = mActionBar.isOverflowPopupNeeded(); 170 if (!needed) { 171 return false; 172 } 173 // Copied from android.widget.ActionMenuPresenter.updateMenuView() 174 ArrayList<MenuItemImpl> menus = mActionBar.getMenuBuilder().getNonActionItems(); 175 ActionMenuPresenter presenter = mActionBar.getActionMenuPresenter(); 176 if (presenter == null) { 177 throw new RuntimeException("Failed to create a Presenter for Action Bar Menus."); 178 } 179 if (presenter.isOverflowReserved() && 180 menus != null) { 181 final int count = menus.size(); 182 if (count == 1) { 183 needed = !menus.get(0).isActionViewExpanded(); 184 } else { 185 needed = count > 0; 186 } 187 } 188 return needed; 189 } 190 191 // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth() 192 private int measureContentWidth(@NonNull ListAdapter adapter) { 193 // Menus don't tend to be long, so this is more sane than it looks. 194 int maxWidth = 0; 195 View itemView = null; 196 int itemType = 0; 197 198 Context context = mActionBar.getPopupContext(); 199 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 200 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 201 final int count = adapter.getCount(); 202 for (int i = 0; i < count; i++) { 203 final int positionType = adapter.getItemViewType(i); 204 if (positionType != itemType) { 205 itemType = positionType; 206 itemView = null; 207 } 208 209 if (mMeasureParent == null) { 210 mMeasureParent = new FrameLayout(context); 211 } 212 213 itemView = adapter.getView(i, itemView, mMeasureParent); 214 itemView.measure(widthMeasureSpec, heightMeasureSpec); 215 216 final int itemWidth = itemView.getMeasuredWidth(); 217 int popupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2, 218 context.getResources().getDimensionPixelSize(R.dimen.config_prefDialogWidth)); 219 if (itemWidth >= popupMaxWidth) { 220 return popupMaxWidth; 221 } else if (itemWidth > maxWidth) { 222 maxWidth = itemWidth; 223 } 224 } 225 226 return maxWidth; 227 } 228 229 static int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) { 230 TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/); 231 return (int) typedValue.getDimension(metrics); 232 } 233 234 // TODO: This is duplicated from RenderSessionImpl. 235 private int getActionBarHeight() { 236 RenderResources resources = mBridgeContext.getRenderResources(); 237 DisplayMetrics metrics = mBridgeContext.getMetrics(); 238 ResourceValue value = resources.findItemInTheme("actionBarSize", true); 239 240 // resolve it 241 value = resources.resolveResValue(value); 242 243 if (value != null) { 244 // get the numerical value, if available 245 TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(), 246 true); 247 if (typedValue != null) { 248 // compute the pixel value based on the display metrics 249 return (int) typedValue.getDimension(metrics); 250 251 } 252 } 253 return 0; 254 } 255} 256