Use appcompat as sources

This commit is contained in:
Victor Shcherb 2015-05-03 14:56:07 +02:00
parent d4263cc5cb
commit e6d2fe81f3
519 changed files with 79009 additions and 528 deletions

View file

@ -142,14 +142,14 @@ android {
buildTypes {
debug {
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
minifyEnabled true
proguardFiles 'proguard-project.txt'
// minifyEnabled true
// proguardFiles 'proguard-project.txt'
signingConfig signingConfigs.development
}
release {
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
minifyEnabled true
proguardFiles 'proguard-project.txt'
// minifyEnabled true
//proguardFiles 'proguard-project.txt'
signingConfig signingConfigs.publishing
}
}
@ -294,6 +294,7 @@ repositories {
}
dependencies {
compile project(path: ":eclipse-compile:appcompat", configuration: "android")
compile project(path: ":OsmAnd-java", configuration: "android")
compile fileTree(
dir: "libs",
@ -305,8 +306,8 @@ dependencies {
"OsmAndCore_android.jar",
"OsmAndCore_wrapper.jar"])
compile "com.github.ksoichiro:android-observablescrollview:1.5.0"
compile "com.android.support:appcompat-v7:21.0.3"
compile "com.github.shell-software:fab:1.0.5"
// compile "com.android.support:appcompat-v7:21.0.3"
// compile "com.github.shell-software:fab:1.0.5"
legacyCompile "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@jar"
qtcoredebugCompile "net.osmand:OsmAndCore_androidNativeDebug:0.1-SNAPSHOT@aar"
qtcoredebugCompile "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar"

Binary file not shown.

View file

@ -96,8 +96,7 @@ public class Version {
}
public static boolean isDeveloperVersion(OsmandApplication ctx){
return "osmand~".equalsIgnoreCase(getAppName(ctx)) || "osmand~f".equalsIgnoreCase(getAppName(ctx)) ;
return getAppName(ctx).contains("~");
}
public static String getVersionForTracker(OsmandApplication ctx) {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 android.support.v7.app;
/**
* @deprecated Use {@link android.support.v7.app.AppCompatActivity} instead.
*/
@Deprecated
public class ActionBarActivity extends AppCompatActivity {
}

View file

@ -0,0 +1,697 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.app;
import android.app.Activity;
import android.app.ActionBar;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.support.v7.appcompat.R;
/**
* This class provides a handy way to tie together the functionality of
* {@link android.support.v4.widget.DrawerLayout} and the framework <code>ActionBar</code> to
* implement the recommended design for navigation drawers.
*
* <p>To use <code>ActionBarDrawerToggle</code>, create one in your Activity and call through
* to the following methods corresponding to your Activity callbacks:</p>
*
* <ul>
* <li>{@link android.app.Activity#onConfigurationChanged(android.content.res.Configuration)
* onConfigurationChanged}
* <li>{@link android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
* onOptionsItemSelected}</li>
* </ul>
*
* <p>Call {@link #syncState()} from your <code>Activity</code>'s
* {@link android.app.Activity#onPostCreate(android.os.Bundle) onPostCreate} to synchronize the
* indicator with the state of the linked DrawerLayout after <code>onRestoreInstanceState</code>
* has occurred.</p>
*
* <p><code>ActionBarDrawerToggle</code> can be used directly as a
* {@link android.support.v4.widget.DrawerLayout.DrawerListener}, or if you are already providing
* your own listener, call through to each of the listener methods from your own.</p>
*
* <p>
* You can customize the the animated toggle by defining the
* {@link android.support.v7.appcompat.R.styleable#DrawerArrowToggle drawerArrowStyle} in your
* ActionBar theme.
*/
public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener {
/**
* Allows an implementing Activity to return an {@link ActionBarDrawerToggle.Delegate} to use
* with ActionBarDrawerToggle.
*/
public interface DelegateProvider {
/**
* @return Delegate to use for ActionBarDrawableToggles, or null if the Activity
* does not wish to override the default behavior.
*/
@Nullable
Delegate getDrawerToggleDelegate();
}
public interface Delegate {
/**
* Set the Action Bar's up indicator drawable and content description.
*
* @param upDrawable - Drawable to set as up indicator
* @param contentDescRes - Content description to set
*/
void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes);
/**
* Set the Action Bar's up indicator content description.
*
* @param contentDescRes - Content description to set
*/
void setActionBarDescription(@StringRes int contentDescRes);
/**
* Returns the drawable to be set as up button when DrawerToggle is disabled
*/
Drawable getThemeUpIndicator();
/**
* Returns the context of ActionBar
*/
Context getActionBarThemedContext();
/**
* Returns whether navigation icon is visible or not.
* Used to print warning messages in case developer forgets to set displayHomeAsUp to true
*/
boolean isNavigationVisible();
}
private final Delegate mActivityImpl;
private final DrawerLayout mDrawerLayout;
private DrawerToggle mSlider;
private Drawable mHomeAsUpIndicator;
private boolean mDrawerIndicatorEnabled = true;
private boolean mHasCustomUpIndicator;
private final int mOpenDrawerContentDescRes;
private final int mCloseDrawerContentDescRes;
// used in toolbar mode when DrawerToggle is disabled
private View.OnClickListener mToolbarNavigationClickListener;
// If developer does not set displayHomeAsUp, DrawerToggle won't show up.
// DrawerToggle logs a warning if this case is detected
private boolean mWarnedForDisplayHomeAsUp = false;
/**
* Construct a new ActionBarDrawerToggle.
*
* <p>The given {@link Activity} will be linked to the specified {@link DrawerLayout} and
* its Actionbar's Up button will be set to a custom drawable.
* <p>This drawable shows a Hamburger icon when drawer is closed and an arrow when drawer
* is open. It animates between these two states as the drawer opens.</p>
*
* <p>String resources must be provided to describe the open/close drawer actions for
* accessibility services.</p>
*
* @param activity The Activity hosting the drawer. Should have an ActionBar.
* @param drawerLayout The DrawerLayout to link to the given Activity's ActionBar
* @param openDrawerContentDescRes A String resource to describe the "open drawer" action
* for accessibility
* @param closeDrawerContentDescRes A String resource to describe the "close drawer" action
* for accessibility
*/
public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout,
@StringRes int openDrawerContentDescRes,
@StringRes int closeDrawerContentDescRes) {
this(activity, null, drawerLayout, null, openDrawerContentDescRes,
closeDrawerContentDescRes);
}
/**
* Construct a new ActionBarDrawerToggle with a Toolbar.
* <p>
* The given {@link Activity} will be linked to the specified {@link DrawerLayout} and
* the Toolbar's navigation icon will be set to a custom drawable. Using this constructor
* will set Toolbar's navigation click listener to toggle the drawer when it is clicked.
* <p>
* This drawable shows a Hamburger icon when drawer is closed and an arrow when drawer
* is open. It animates between these two states as the drawer opens.
* <p>
* String resources must be provided to describe the open/close drawer actions for
* accessibility services.
* <p>
* Please use {@link #ActionBarDrawerToggle(Activity, DrawerLayout, int, int)} if you are
* setting the Toolbar as the ActionBar of your activity.
*
* @param activity The Activity hosting the drawer.
* @param toolbar The toolbar to use if you have an independent Toolbar.
* @param drawerLayout The DrawerLayout to link to the given Activity's ActionBar
* @param openDrawerContentDescRes A String resource to describe the "open drawer" action
* for accessibility
* @param closeDrawerContentDescRes A String resource to describe the "close drawer" action
* for accessibility
*/
public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout,
Toolbar toolbar, @StringRes int openDrawerContentDescRes,
@StringRes int closeDrawerContentDescRes) {
this(activity, toolbar, drawerLayout, null, openDrawerContentDescRes,
closeDrawerContentDescRes);
}
/**
* In the future, we can make this constructor public if we want to let developers customize
* the
* animation.
*/
<T extends Drawable & DrawerToggle> ActionBarDrawerToggle(Activity activity, Toolbar toolbar,
DrawerLayout drawerLayout, T slider,
@StringRes int openDrawerContentDescRes,
@StringRes int closeDrawerContentDescRes) {
if (toolbar != null) {
mActivityImpl = new ToolbarCompatDelegate(toolbar);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mDrawerIndicatorEnabled) {
toggle();
} else if (mToolbarNavigationClickListener != null) {
mToolbarNavigationClickListener.onClick(v);
}
}
});
} else if (activity instanceof DelegateProvider) { // Allow the Activity to provide an impl
mActivityImpl = ((DelegateProvider) activity).getDrawerToggleDelegate();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mActivityImpl = new JellybeanMr2Delegate(activity);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mActivityImpl = new HoneycombDelegate(activity);
} else {
mActivityImpl = new DummyDelegate(activity);
}
mDrawerLayout = drawerLayout;
mOpenDrawerContentDescRes = openDrawerContentDescRes;
mCloseDrawerContentDescRes = closeDrawerContentDescRes;
if (slider == null) {
mSlider = new DrawerArrowDrawableToggle(activity,
mActivityImpl.getActionBarThemedContext());
} else {
mSlider = slider;
}
mHomeAsUpIndicator = getThemeUpIndicator();
}
/**
* Synchronize the state of the drawer indicator/affordance with the linked DrawerLayout.
*
* <p>This should be called from your <code>Activity</code>'s
* {@link Activity#onPostCreate(android.os.Bundle) onPostCreate} method to synchronize after
* the DrawerLayout's instance state has been restored, and any other time when the state
* may have diverged in such a way that the ActionBarDrawerToggle was not notified.
* (For example, if you stop forwarding appropriate drawer events for a period of time.)</p>
*/
public void syncState() {
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mSlider.setPosition(1);
} else {
mSlider.setPosition(0);
}
if (mDrawerIndicatorEnabled) {
setActionBarUpIndicator((Drawable) mSlider,
mDrawerLayout.isDrawerOpen(GravityCompat.START) ?
mCloseDrawerContentDescRes : mOpenDrawerContentDescRes);
}
}
/**
* This method should always be called by your <code>Activity</code>'s
* {@link Activity#onConfigurationChanged(android.content.res.Configuration)
* onConfigurationChanged}
* method.
*
* @param newConfig The new configuration
*/
public void onConfigurationChanged(Configuration newConfig) {
// Reload drawables that can change with configuration
if (!mHasCustomUpIndicator) {
mHomeAsUpIndicator = getThemeUpIndicator();
}
syncState();
}
/**
* This method should be called by your <code>Activity</code>'s
* {@link Activity#onOptionsItemSelected(android.view.MenuItem) onOptionsItemSelected} method.
* If it returns true, your <code>onOptionsItemSelected</code> method should return true and
* skip further processing.
*
* @param item the MenuItem instance representing the selected menu item
* @return true if the event was handled and further processing should not occur
*/
public boolean onOptionsItemSelected(MenuItem item) {
if (item != null && item.getItemId() == android.R.id.home && mDrawerIndicatorEnabled) {
toggle();
return true;
}
return false;
}
private void toggle() {
if (mDrawerLayout.isDrawerVisible(GravityCompat.START)) {
mDrawerLayout.closeDrawer(GravityCompat.START);
} else {
mDrawerLayout.openDrawer(GravityCompat.START);
}
}
/**
* Set the up indicator to display when the drawer indicator is not
* enabled.
* <p>
* If you pass <code>null</code> to this method, the default drawable from
* the theme will be used.
*
* @param indicator A drawable to use for the up indicator, or null to use
* the theme's default
* @see #setDrawerIndicatorEnabled(boolean)
*/
public void setHomeAsUpIndicator(Drawable indicator) {
if (indicator == null) {
mHomeAsUpIndicator = getThemeUpIndicator();
mHasCustomUpIndicator = false;
} else {
mHomeAsUpIndicator = indicator;
mHasCustomUpIndicator = true;
}
if (!mDrawerIndicatorEnabled) {
setActionBarUpIndicator(mHomeAsUpIndicator, 0);
}
}
/**
* Set the up indicator to display when the drawer indicator is not
* enabled.
* <p>
* If you pass 0 to this method, the default drawable from the theme will
* be used.
*
* @param resId Resource ID of a drawable to use for the up indicator, or 0
* to use the theme's default
* @see #setDrawerIndicatorEnabled(boolean)
*/
public void setHomeAsUpIndicator(int resId) {
Drawable indicator = null;
if (resId != 0) {
indicator = mDrawerLayout.getResources().getDrawable(resId);
}
setHomeAsUpIndicator(indicator);
}
/**
* @return true if the enhanced drawer indicator is enabled, false otherwise
* @see #setDrawerIndicatorEnabled(boolean)
*/
public boolean isDrawerIndicatorEnabled() {
return mDrawerIndicatorEnabled;
}
/**
* Enable or disable the drawer indicator. The indicator defaults to enabled.
*
* <p>When the indicator is disabled, the <code>ActionBar</code> will revert to displaying
* the home-as-up indicator provided by the <code>Activity</code>'s theme in the
* <code>android.R.attr.homeAsUpIndicator</code> attribute instead of the animated
* drawer glyph.</p>
*
* @param enable true to enable, false to disable
*/
public void setDrawerIndicatorEnabled(boolean enable) {
if (enable != mDrawerIndicatorEnabled) {
if (enable) {
setActionBarUpIndicator((Drawable) mSlider,
mDrawerLayout.isDrawerOpen(GravityCompat.START) ?
mCloseDrawerContentDescRes : mOpenDrawerContentDescRes);
} else {
setActionBarUpIndicator(mHomeAsUpIndicator, 0);
}
mDrawerIndicatorEnabled = enable;
}
}
/**
* {@link DrawerLayout.DrawerListener} callback method. If you do not use your
* ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call
* through to this method from your own listener object.
*
* @param drawerView The child view that was moved
* @param slideOffset The new offset of this drawer within its range, from 0-1
*/
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
mSlider.setPosition(Math.min(1f, Math.max(0, slideOffset)));
}
/**
* {@link DrawerLayout.DrawerListener} callback method. If you do not use your
* ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call
* through to this method from your own listener object.
*
* @param drawerView Drawer view that is now open
*/
@Override
public void onDrawerOpened(View drawerView) {
mSlider.setPosition(1);
if (mDrawerIndicatorEnabled) {
setActionBarDescription(mCloseDrawerContentDescRes);
}
}
/**
* {@link DrawerLayout.DrawerListener} callback method. If you do not use your
* ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call
* through to this method from your own listener object.
*
* @param drawerView Drawer view that is now closed
*/
@Override
public void onDrawerClosed(View drawerView) {
mSlider.setPosition(0);
if (mDrawerIndicatorEnabled) {
setActionBarDescription(mOpenDrawerContentDescRes);
}
}
/**
* {@link DrawerLayout.DrawerListener} callback method. If you do not use your
* ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call
* through to this method from your own listener object.
*
* @param newState The new drawer motion state
*/
@Override
public void onDrawerStateChanged(int newState) {
}
/**
* Returns the fallback listener for Navigation icon click events.
*
* @return The click listener which receives Navigation click events from Toolbar when
* drawer indicator is disabled.
* @see #setToolbarNavigationClickListener(android.view.View.OnClickListener)
* @see #setDrawerIndicatorEnabled(boolean)
* @see #isDrawerIndicatorEnabled()
*/
public View.OnClickListener getToolbarNavigationClickListener() {
return mToolbarNavigationClickListener;
}
/**
* When DrawerToggle is constructed with a Toolbar, it sets the click listener on
* the Navigation icon. If you want to listen for clicks on the Navigation icon when
* DrawerToggle is disabled ({@link #setDrawerIndicatorEnabled(boolean)}, you should call this
* method with your listener and DrawerToggle will forward click events to that listener
* when drawer indicator is disabled.
*
* @see #setDrawerIndicatorEnabled(boolean)
*/
public void setToolbarNavigationClickListener(
View.OnClickListener onToolbarNavigationClickListener) {
mToolbarNavigationClickListener = onToolbarNavigationClickListener;
}
void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
if (!mWarnedForDisplayHomeAsUp && !mActivityImpl.isNavigationVisible()) {
Log.w("ActionBarDrawerToggle", "DrawerToggle may not show up because NavigationIcon"
+ " is not visible. You may need to call "
+ "actionbar.setDisplayHomeAsUpEnabled(true);");
mWarnedForDisplayHomeAsUp = true;
}
mActivityImpl.setActionBarUpIndicator(upDrawable, contentDescRes);
}
void setActionBarDescription(int contentDescRes) {
mActivityImpl.setActionBarDescription(contentDescRes);
}
Drawable getThemeUpIndicator() {
return mActivityImpl.getThemeUpIndicator();
}
static class DrawerArrowDrawableToggle extends DrawerArrowDrawable
implements DrawerToggle {
private final Activity mActivity;
public DrawerArrowDrawableToggle(Activity activity, Context themedContext) {
super(themedContext);
mActivity = activity;
}
public void setPosition(float position) {
if (position == 1f) {
setVerticalMirror(true);
} else if (position == 0f) {
setVerticalMirror(false);
}
super.setProgress(position);
}
@Override
boolean isLayoutRtl() {
return ViewCompat.getLayoutDirection(mActivity.getWindow().getDecorView())
== ViewCompat.LAYOUT_DIRECTION_RTL;
}
public float getPosition() {
return super.getProgress();
}
}
/**
* Interface for toggle drawables. Can be public in the future
*/
static interface DrawerToggle {
public void setPosition(float position);
public float getPosition();
}
/**
* Delegate if SDK version is between honeycomb and JBMR2
*/
private static class HoneycombDelegate implements Delegate {
final Activity mActivity;
ActionBarDrawerToggleHoneycomb.SetIndicatorInfo mSetIndicatorInfo;
private HoneycombDelegate(Activity activity) {
mActivity = activity;
}
@Override
public Drawable getThemeUpIndicator() {
return ActionBarDrawerToggleHoneycomb.getThemeUpIndicator(mActivity);
}
@Override
public Context getActionBarThemedContext() {
final ActionBar actionBar = mActivity.getActionBar();
final Context context;
if (actionBar != null) {
context = actionBar.getThemedContext();
} else {
context = mActivity;
}
return context;
}
@Override
public boolean isNavigationVisible() {
final ActionBar actionBar = mActivity.getActionBar();
return actionBar != null
&& (actionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
}
@Override
public void setActionBarUpIndicator(Drawable themeImage, int contentDescRes) {
mActivity.getActionBar().setDisplayShowHomeEnabled(true);
mSetIndicatorInfo = ActionBarDrawerToggleHoneycomb.setActionBarUpIndicator(
mSetIndicatorInfo, mActivity, themeImage, contentDescRes);
mActivity.getActionBar().setDisplayShowHomeEnabled(false);
}
@Override
public void setActionBarDescription(int contentDescRes) {
mSetIndicatorInfo = ActionBarDrawerToggleHoneycomb.setActionBarDescription(
mSetIndicatorInfo, mActivity, contentDescRes);
}
}
/**
* Delegate if SDK version is JB MR2 or newer
*/
private static class JellybeanMr2Delegate implements Delegate {
final Activity mActivity;
private JellybeanMr2Delegate(Activity activity) {
mActivity = activity;
}
@Override
public Drawable getThemeUpIndicator() {
final TypedArray a = getActionBarThemedContext().obtainStyledAttributes(null,
new int[]{android.R.attr.homeAsUpIndicator}, android.R.attr.actionBarStyle, 0);
final Drawable result = a.getDrawable(0);
a.recycle();
return result;
}
@Override
public Context getActionBarThemedContext() {
final ActionBar actionBar = mActivity.getActionBar();
final Context context;
if (actionBar != null) {
context = actionBar.getThemedContext();
} else {
context = mActivity;
}
return context;
}
@Override
public boolean isNavigationVisible() {
final ActionBar actionBar = mActivity.getActionBar();
return actionBar != null &&
(actionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
}
@Override
public void setActionBarUpIndicator(Drawable drawable, int contentDescRes) {
final ActionBar actionBar = mActivity.getActionBar();
if (actionBar != null) {
actionBar.setHomeAsUpIndicator(drawable);
actionBar.setHomeActionContentDescription(contentDescRes);
}
}
@Override
public void setActionBarDescription(int contentDescRes) {
final ActionBar actionBar = mActivity.getActionBar();
if (actionBar != null) {
actionBar.setHomeActionContentDescription(contentDescRes);
}
}
}
/**
* Used when DrawerToggle is initialized with a Toolbar
*/
static class ToolbarCompatDelegate implements Delegate {
final Toolbar mToolbar;
final Drawable mDefaultUpIndicator;
final CharSequence mDefaultContentDescription;
ToolbarCompatDelegate(Toolbar toolbar) {
mToolbar = toolbar;
mDefaultUpIndicator = toolbar.getNavigationIcon();
mDefaultContentDescription = toolbar.getNavigationContentDescription();
}
@Override
public void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes) {
mToolbar.setNavigationIcon(upDrawable);
setActionBarDescription(contentDescRes);
}
@Override
public void setActionBarDescription(@StringRes int contentDescRes) {
if (contentDescRes == 0) {
mToolbar.setNavigationContentDescription(mDefaultContentDescription);
} else {
mToolbar.setNavigationContentDescription(contentDescRes);
}
}
@Override
public Drawable getThemeUpIndicator() {
return mDefaultUpIndicator;
}
@Override
public Context getActionBarThemedContext() {
return mToolbar.getContext();
}
@Override
public boolean isNavigationVisible() {
return true;
}
}
/**
* Fallback delegate
*/
static class DummyDelegate implements Delegate {
final Activity mActivity;
DummyDelegate(Activity activity) {
mActivity = activity;
}
@Override
public void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes) {
}
@Override
public void setActionBarDescription(@StringRes int contentDescRes) {
}
@Override
public Drawable getThemeUpIndicator() {
return null;
}
@Override
public Context getActionBarThemedContext() {
return mActivity;
}
@Override
public boolean isNavigationVisible() {
return true;
}
}
}

View file

@ -0,0 +1,139 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.app;
import android.R;
import android.app.ActionBar;
import android.app.Activity;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.lang.reflect.Method;
/**
* This class encapsulates some awful hacks.
*
* Before JB-MR2 (API 18) it was not possible to change the home-as-up indicator glyph
* in an action bar without some really gross hacks. Since the MR2 SDK is not published as of
* this writing, the new API is accessed via reflection here if available.
*
* Moved from Support-v4
*/
class ActionBarDrawerToggleHoneycomb {
private static final String TAG = "ActionBarDrawerToggleHoneycomb";
private static final int[] THEME_ATTRS = new int[] {
R.attr.homeAsUpIndicator
};
public static SetIndicatorInfo setActionBarUpIndicator(SetIndicatorInfo info, Activity activity,
Drawable drawable, int contentDescRes) {
if (true || info == null) {
info = new SetIndicatorInfo(activity);
}
if (info.setHomeAsUpIndicator != null) {
try {
final ActionBar actionBar = activity.getActionBar();
info.setHomeAsUpIndicator.invoke(actionBar, drawable);
info.setHomeActionContentDescription.invoke(actionBar, contentDescRes);
} catch (Exception e) {
Log.w(TAG, "Couldn't set home-as-up indicator via JB-MR2 API", e);
}
} else if (info.upIndicatorView != null) {
info.upIndicatorView.setImageDrawable(drawable);
} else {
Log.w(TAG, "Couldn't set home-as-up indicator");
}
return info;
}
public static SetIndicatorInfo setActionBarDescription(SetIndicatorInfo info, Activity activity,
int contentDescRes) {
if (info == null) {
info = new SetIndicatorInfo(activity);
}
if (info.setHomeAsUpIndicator != null) {
try {
final ActionBar actionBar = activity.getActionBar();
info.setHomeActionContentDescription.invoke(actionBar, contentDescRes);
if (Build.VERSION.SDK_INT <= 19) {
// For API 19 and earlier, we need to manually force the
// action bar to generate a new content description.
actionBar.setSubtitle(actionBar.getSubtitle());
}
} catch (Exception e) {
Log.w(TAG, "Couldn't set content description via JB-MR2 API", e);
}
}
return info;
}
public static Drawable getThemeUpIndicator(Activity activity) {
final TypedArray a = activity.obtainStyledAttributes(THEME_ATTRS);
final Drawable result = a.getDrawable(0);
a.recycle();
return result;
}
static class SetIndicatorInfo {
public Method setHomeAsUpIndicator;
public Method setHomeActionContentDescription;
public ImageView upIndicatorView;
SetIndicatorInfo(Activity activity) {
try {
setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod("setHomeAsUpIndicator",
Drawable.class);
setHomeActionContentDescription = ActionBar.class.getDeclaredMethod(
"setHomeActionContentDescription", Integer.TYPE);
// If we got the method we won't need the stuff below.
return;
} catch (NoSuchMethodException e) {
// Oh well. We'll use the other mechanism below instead.
}
final View home = activity.findViewById(android.R.id.home);
if (home == null) {
// Action bar doesn't have a known configuration, an OEM messed with things.
return;
}
final ViewGroup parent = (ViewGroup) home.getParent();
final int childCount = parent.getChildCount();
if (childCount != 2) {
// No idea which one will be the right one, an OEM messed with things.
return;
}
final View first = parent.getChildAt(0);
final View second = parent.getChildAt(1);
final View up = first.getId() == android.R.id.home ? second : first;
if (up instanceof ImageView) {
// Jackpot! (Probably...)
upIndicatorView = (ImageView) up;
}
}
}
}

View file

@ -0,0 +1,416 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v7.app;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Base class for activities that use the
* <a href="{@docRoot}tools/extras/support-library.html">support library</a> action bar features.
*
* <p>You can add an {@link android.support.v7.app.ActionBar} to your activity when running on API level 7 or higher
* by extending this class for your activity and setting the activity theme to
* {@link android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} or a similar theme.
*
* <div class="special reference">
* <h3>Developer Guides</h3>
*
* <p>For information about how to use the action bar, including how to add action items, navigation
* modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action
* Bar</a> API guide.</p>
* </div>
*/
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
super.onCreate(savedInstanceState);
getDelegate().onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
/**
* Support library version of {@link android.app.Activity#getActionBar}.
*
* <p>Retrieve a reference to this activity's ActionBar.
*
* @return The Activity's ActionBar, or null if it does not have one.
*/
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
/**
* Set a {@link android.widget.Toolbar Toolbar} to act as the {@link android.support.v7.app.ActionBar} for this
* Activity window.
*
* <p>When set to a non-null value the {@link #getActionBar()} method will return
* an {@link android.support.v7.app.ActionBar} object that can be used to control the given toolbar as if it were
* a traditional window decor action bar. The toolbar's menu will be populated with the
* Activity's options menu and the navigation button will be wired through the standard
* {@link android.R.id#home home} menu select action.</p>
*
* <p>In order to use a Toolbar within the Activity's window content the application
* must not request the window feature {@link android.view.Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
*
* @param toolbar Toolbar to set as the Activity's action bar
*/
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
if (super.onMenuItemSelected(featureId, item)) {
return true;
}
final ActionBar ab = getSupportActionBar();
if (item.getItemId() == android.R.id.home && ab != null &&
(ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
return onSupportNavigateUp();
}
return false;
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
/**
* Enable extended support library window features.
* <p>
* This is a convenience for calling
* {@link android.view.Window#requestFeature getWindow().requestFeature()}.
* </p>
*
* @param featureId The desired feature as defined in
* {@link android.view.Window} or {@link android.support.v4.view.WindowCompat}.
* @return Returns true if the requested feature is supported and now enabled.
*
* @see android.app.Activity#requestWindowFeature
* @see android.view.Window#requestFeature
*/
public boolean supportRequestWindowFeature(int featureId) {
return getDelegate().requestWindowFeature(featureId);
}
@Override
public void supportInvalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
/**
* @hide
*/
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
/**
* Notifies the Activity that a support action mode has been started.
* Activity subclasses overriding this method should call the superclass implementation.
*
* @param mode The new action mode.
*/
public void onSupportActionModeStarted(ActionMode mode) {
}
/**
* Notifies the activity that a support action mode has finished.
* Activity subclasses overriding this method should call the superclass implementation.
*
* @param mode The action mode that just finished.
*/
public void onSupportActionModeFinished(ActionMode mode) {
}
public ActionMode startSupportActionMode(ActionMode.Callback callback) {
return getDelegate().startSupportActionMode(callback);
}
/**
* @deprecated Progress bars are no longer provided in AppCompat.
*/
@Deprecated
public void setSupportProgressBarVisibility(boolean visible) {
}
/**
* @deprecated Progress bars are no longer provided in AppCompat.
*/
@Deprecated
public void setSupportProgressBarIndeterminateVisibility(boolean visible) {
}
/**
* @deprecated Progress bars are no longer provided in AppCompat.
*/
@Deprecated
public void setSupportProgressBarIndeterminate(boolean indeterminate) {
}
/**
* @deprecated Progress bars are no longer provided in AppCompat.
*/
@Deprecated
public void setSupportProgress(int progress) {
}
/**
* Support version of {@link #onCreateNavigateUpTaskStack(android.app.TaskStackBuilder)}.
* This method will be called on all platform versions.
*
* Define the synthetic task stack that will be generated during Up navigation from
* a different task.
*
* <p>The default implementation of this method adds the parent chain of this activity
* as specified in the manifest to the supplied {@link android.support.v4.app.TaskStackBuilder}. Applications
* may choose to override this method to construct the desired task stack in a different
* way.</p>
*
* <p>This method will be invoked by the default implementation of {@link #onNavigateUp()}
* if {@link #shouldUpRecreateTask(android.content.Intent)} returns true when supplied with the intent
* returned by {@link #getParentActivityIntent()}.</p>
*
* <p>Applications that wish to supply extra Intent parameters to the parent stack defined
* by the manifest should override
* {@link #onPrepareSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)}.</p>
*
* @param builder An empty TaskStackBuilder - the application should add intents representing
* the desired task stack
*/
public void onCreateSupportNavigateUpTaskStack(TaskStackBuilder builder) {
builder.addParentStack(this);
}
/**
* Support version of {@link #onPrepareNavigateUpTaskStack(android.app.TaskStackBuilder)}.
* This method will be called on all platform versions.
*
* Prepare the synthetic task stack that will be generated during Up navigation
* from a different task.
*
* <p>This method receives the {@link android.support.v4.app.TaskStackBuilder} with the constructed series of
* Intents as generated by {@link #onCreateSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)}.
* If any extra data should be added to these intents before launching the new task,
* the application should override this method and add that data here.</p>
*
* @param builder A TaskStackBuilder that has been populated with Intents by
* onCreateNavigateUpTaskStack.
*/
public void onPrepareSupportNavigateUpTaskStack(TaskStackBuilder builder) {
}
/**
* This method is called whenever the user chooses to navigate Up within your application's
* activity hierarchy from the action bar.
*
* <p>If a parent was specified in the manifest for this activity or an activity-alias to it,
* default Up navigation will be handled automatically. See
* {@link #getSupportParentActivityIntent()} for how to specify the parent. If any activity
* along the parent chain requires extra Intent arguments, the Activity subclass
* should override the method {@link #onPrepareSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)}
* to supply those arguments.</p>
*
* <p>See <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and
* Back Stack</a> from the developer guide and
* <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> from the design guide
* for more information about navigating within your app.</p>
*
* <p>See the {@link android.support.v4.app.TaskStackBuilder} class and the Activity methods
* {@link #getSupportParentActivityIntent()}, {@link #supportShouldUpRecreateTask(android.content.Intent)}, and
* {@link #supportNavigateUpTo(android.content.Intent)} for help implementing custom Up navigation.</p>
*
* @return true if Up navigation completed successfully and this Activity was finished,
* false otherwise.
*/
public boolean onSupportNavigateUp() {
Intent upIntent = getSupportParentActivityIntent();
if (upIntent != null) {
if (supportShouldUpRecreateTask(upIntent)) {
TaskStackBuilder b = TaskStackBuilder.create(this);
onCreateSupportNavigateUpTaskStack(b);
onPrepareSupportNavigateUpTaskStack(b);
b.startActivities();
try {
ActivityCompat.finishAffinity(this);
} catch (IllegalStateException e) {
// This can only happen on 4.1+, when we don't have a parent or a result set.
// In that case we should just finish().
finish();
}
} else {
// This activity is part of the application's task, so simply
// navigate up to the hierarchical parent activity.
supportNavigateUpTo(upIntent);
}
return true;
}
return false;
}
/**
* Obtain an {@link android.content.Intent} that will launch an explicit target activity
* specified by sourceActivity's {@link android.support.v4.app.NavUtils#PARENT_ACTIVITY} &lt;meta-data&gt;
* element in the application's manifest. If the device is running
* Jellybean or newer, the android:parentActivityName attribute will be preferred
* if it is present.
*
* @return a new Intent targeting the defined parent activity of sourceActivity
*/
public Intent getSupportParentActivityIntent() {
return NavUtils.getParentActivityIntent(this);
}
/**
* Returns true if sourceActivity should recreate the task when navigating 'up'
* by using targetIntent.
*
* <p>If this method returns false the app can trivially call
* {@link #supportNavigateUpTo(android.content.Intent)} using the same parameters to correctly perform
* up navigation. If this method returns false, the app should synthesize a new task stack
* by using {@link android.support.v4.app.TaskStackBuilder} or another similar mechanism to perform up navigation.</p>
*
* @param targetIntent An intent representing the target destination for up navigation
* @return true if navigating up should recreate a new task stack, false if the same task
* should be used for the destination
*/
public boolean supportShouldUpRecreateTask(Intent targetIntent) {
return NavUtils.shouldUpRecreateTask(this, targetIntent);
}
/**
* Navigate from sourceActivity to the activity specified by upIntent, finishing sourceActivity
* in the process. upIntent will have the flag {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP} set
* by this method, along with any others required for proper up navigation as outlined
* in the Android Design Guide.
*
* <p>This method should be used when performing up navigation from within the same task
* as the destination. If up navigation should cross tasks in some cases, see
* {@link #supportShouldUpRecreateTask(android.content.Intent)}.</p>
*
* @param upIntent An intent representing the target destination for up navigation
*/
public void supportNavigateUpTo(Intent upIntent) {
NavUtils.navigateUpTo(this, upIntent);
}
@Override
public void onContentChanged() {
// Call onSupportContentChanged() for legacy reasons
onSupportContentChanged();
}
/**
* @deprecated Use {@link #onContentChanged()} instead.
*/
@Deprecated
public void onSupportContentChanged() {
}
@Nullable
@Override
public ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
return getDelegate().getDrawerToggleDelegate();
}
/**
* @return The {@link AppCompatDelegate} being used by this Activity.
*/
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.app;
import android.support.v7.view.ActionMode;
/**
* Implemented this in order for AppCompat to be able to callback in certain situations.
* <p>
* This should be provided to
* {@link AppCompatDelegate#create(android.app.Activity, AppCompatCallback)}.
*/
public interface AppCompatCallback {
/**
* Called when a support action mode has been started.
*
* @param mode The new action mode.
*/
void onSupportActionModeStarted(ActionMode mode);
/**
* Called when a support action mode has finished.
*
* @param mode The action mode that just finished.
*/
void onSupportActionModeFinished(ActionMode mode);
}

View file

@ -0,0 +1,251 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.app;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* This class represents a delegate which you can use to extend AppCompat's support to any
* {@link android.app.Activity}.
* <p>
* When using an {@link AppCompatDelegate}, you should any methods exposed in it rather than the
* {@link android.app.Activity} method of the same name. This applies to:
* <ul>
* <li>{@link #addContentView(android.view.View, android.view.ViewGroup.LayoutParams)}</li>
* <li>{@link #setContentView(int)}</li>
* <li>{@link #setContentView(android.view.View)}</li>
* <li>{@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}</li>
* <li>{@link #requestWindowFeature(int)}</li>
* <li>{@link #invalidateOptionsMenu()}</li>
* <li>{@link #startSupportActionMode(android.support.v7.view.ActionMode.Callback)}</li>
* <li>{@link #setSupportActionBar(android.support.v7.widget.Toolbar)}</li>
* <li>{@link #getSupportActionBar()}</li>
* <li>{@link #getMenuInflater()}</li>
* </ul>
* There also some Activity lifecycle methods which should be proxied to the delegate:
* <ul>
* <li>{@link #onCreate(android.os.Bundle)}</li>
* <li>{@link #onPostCreate(android.os.Bundle)}</li>
* <li>{@link #onConfigurationChanged(android.content.res.Configuration)}</li>
* <li>{@link #setTitle(CharSequence)}</li>
* <li>{@link #onStop()}</li>
* <li>{@link #onDestroy()}</li>
* </ul>
* <p>
* An {@link Activity} can only be linked with one {@link AppCompatDelegate} instance,
* so the instance returned from {@link #create(Activity, AppCompatCallback)} should be kept
* until the Activity is destroyed.
*/
public abstract class AppCompatDelegate {
static final String TAG = "AppCompatDelegate";
/**
* Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}.
*
* @param callback An optional callback for AppCompat specific events
*/
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return new AppCompatDelegateImplV11(activity, activity.getWindow(), callback);
} else {
return new AppCompatDelegateImplV7(activity, activity.getWindow(), callback);
}
}
/**
* Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code dialog}.
*
* @param callback An optional callback for AppCompat specific events
*/
public static AppCompatDelegate create(Dialog dialog, AppCompatCallback callback) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return new AppCompatDelegateImplV11(dialog.getContext(), dialog.getWindow(), callback);
} else {
return new AppCompatDelegateImplV7(dialog.getContext(), dialog.getWindow(), callback);
}
}
/**
* Private constructor
*/
AppCompatDelegate() {}
/**
* Support library version of {@link Activity#getActionBar}.
*
* @return AppCompat's action bar, or null if it does not have one.
*/
public abstract ActionBar getSupportActionBar();
/**
* Set a {@link Toolbar} to act as the {@link ActionBar} for this delegate.
*
* <p>When set to a non-null value the {@link #getSupportActionBar()} ()} method will return
* an {@link ActionBar} object that can be used to control the given toolbar as if it were
* a traditional window decor action bar. The toolbar's menu will be populated with the
* Activity's options menu and the navigation button will be wired through the standard
* {@link android.R.id#home home} menu select action.</p>
*
* <p>In order to use a Toolbar within the Activity's window content the application
* must not request the window feature
* {@link android.view.Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
*
* @param toolbar Toolbar to set as the Activity's action bar
*/
public abstract void setSupportActionBar(Toolbar toolbar);
/**
* Return the value of this call from your {@link Activity#getMenuInflater()}
*/
public abstract MenuInflater getMenuInflater();
/**
* Should be called from {@link Activity#onCreate Activity.onCreate()}
*/
public abstract void onCreate(Bundle savedInstanceState);
/**
* Should be called from {@link Activity#onPostCreate(android.os.Bundle)}
*/
public abstract void onPostCreate(Bundle savedInstanceState);
/**
* Should be called from
* {@link Activity#onConfigurationChanged}
*/
public abstract void onConfigurationChanged(Configuration newConfig);
/**
* Should be called from {@link Activity#onStop Activity.onStop()}
*/
public abstract void onStop();
/**
* Should be called from {@link Activity#onPostResume()}
*/
public abstract void onPostResume();
/**
* Should be called instead of {@link Activity#setContentView(android.view.View)}}
*/
public abstract void setContentView(View v);
/**
* Should be called instead of {@link Activity#setContentView(int)}}
*/
public abstract void setContentView(int resId);
/**
* Should be called instead of
* {@link Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}}
*/
public abstract void setContentView(View v, ViewGroup.LayoutParams lp);
/**
* Should be called instead of
* {@link Activity#addContentView(android.view.View, android.view.ViewGroup.LayoutParams)}}
*/
public abstract void addContentView(View v, ViewGroup.LayoutParams lp);
/**
* Should be called from {@link Activity#onTitleChanged(CharSequence, int)}}
*/
public abstract void setTitle(CharSequence title);
/**
* Should be called from {@link Activity#invalidateOptionsMenu()}} or
* {@link FragmentActivity#supportInvalidateOptionsMenu()}.
*/
public abstract void invalidateOptionsMenu();
/**
* Should be called from {@link Activity#onDestroy()}
*/
public abstract void onDestroy();
/**
* Returns an {@link ActionBarDrawerToggle.Delegate} which can be returned from your Activity
* if it implements {@link ActionBarDrawerToggle.DelegateProvider}.
*/
public abstract ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
/**
* Enable extended window features. This should be called instead of
* {@link android.app.Activity#requestWindowFeature(int)} or
* {@link android.view.Window#requestFeature getWindow().requestFeature()}.
*
* @param featureId The desired feature as defined in {@link android.view.Window}.
* @return Returns true if the requested feature is supported and now
* enabled.
*/
public abstract boolean requestWindowFeature(int featureId);
/**
* Start an action mode.
*
* @param callback Callback that will manage lifecycle events for this context mode
* @return The ContextMode that was started, or null if it was canceled
*/
public abstract ActionMode startSupportActionMode(ActionMode.Callback callback);
/**
* Installs AppCompat's {@link android.view.LayoutInflater} Factory so that it can replace
* the framework widgets with compatible tinted versions. This should be called before
* {@code super.onCreate()} as so:
* <pre class="prettyprint">
* protected void onCreate(Bundle savedInstanceState) {
* getDelegate().installViewFactory();
* super.onCreate(savedInstanceState);
* getDelegate().onCreate(savedInstanceState);
*
* // ...
* }
* </pre>
* If you are using your own {@link android.view.LayoutInflater.Factory Factory} or
* {@link android.view.LayoutInflater.Factory2 Factory2} then you can omit this call, and instead call
* {@link #createView(android.view.View, String, android.content.Context, android.util.AttributeSet)}
* from your factory to return any compatible widgets.
*/
public abstract void installViewFactory();
/**
* This should be called from a
* {@link android.support.v4.view.LayoutInflaterFactory LayoutInflaterFactory} in order
* to return tint-aware widgets.
* <p>
* This is only needed if you are using your own
* {@link android.view.LayoutInflater LayoutInflater} factory, and have therefore not
* installed the default factory via {@link #installViewFactory()}.
*/
public abstract View createView(View parent, String name, @NonNull Context context,
@NonNull AttributeSet attrs);
}

View file

@ -0,0 +1,325 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.app;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.appcompat.R;
import android.support.v7.internal.view.SupportMenuInflater;
import android.support.v7.internal.view.WindowCallbackWrapper;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.widget.TintTypedArray;
import android.support.v7.view.ActionMode;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.Window;
abstract class AppCompatDelegateImplBase extends AppCompatDelegate {
final Context mContext;
final Window mWindow;
final Window.Callback mOriginalWindowCallback;
final AppCompatCallback mAppCompatCallback;
private ActionBar mActionBar;
private MenuInflater mMenuInflater;
// true if this activity has an action bar.
boolean mHasActionBar;
// true if this activity's action bar overlays other activity content.
boolean mOverlayActionBar;
// true if this any action modes should overlay the activity content
boolean mOverlayActionMode;
// true if this activity is floating (e.g. Dialog)
boolean mIsFloating;
// true if this activity has no title
boolean mWindowNoTitle;
private CharSequence mTitle;
private boolean mIsDestroyed;
AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) {
mContext = context;
mWindow = window;
mAppCompatCallback = callback;
mOriginalWindowCallback = mWindow.getCallback();
if (mOriginalWindowCallback instanceof AppCompatWindowCallback) {
throw new IllegalStateException(
"AppCompat has already installed itself into the Window");
}
// Now install the new callback
mWindow.setCallback(new AppCompatWindowCallback(mOriginalWindowCallback));
}
abstract ActionBar createSupportActionBar();
@Override
public ActionBar getSupportActionBar() {
// The Action Bar should be lazily created as hasActionBar
// could change after onCreate
if (mHasActionBar) {
if (mActionBar == null) {
mActionBar = createSupportActionBar();
}
}
return mActionBar;
}
final ActionBar peekSupportActionBar() {
return mActionBar;
}
final void setSupportActionBar(ActionBar actionBar) {
mActionBar = actionBar;
}
@Override
public MenuInflater getMenuInflater() {
if (mMenuInflater == null) {
mMenuInflater = new SupportMenuInflater(getActionBarThemedContext());
}
return mMenuInflater;
}
@Override
public void onCreate(Bundle savedInstanceState) {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
if (!a.hasValue(R.styleable.Theme_windowActionBar)) {
a.recycle();
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
if (a.getBoolean(R.styleable.Theme_windowActionBar, false)) {
mHasActionBar = true;
}
if (a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false)) {
mOverlayActionBar = true;
}
if (a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false)) {
mOverlayActionMode = true;
}
mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false);
mWindowNoTitle = a.getBoolean(R.styleable.Theme_windowNoTitle, false);
a.recycle();
}
// Methods used to create and respond to options menu
abstract boolean onPanelClosed(int featureId, Menu menu);
abstract boolean onMenuOpened(int featureId, Menu menu);
abstract boolean dispatchKeyEvent(KeyEvent event);
abstract boolean onKeyShortcut(int keyCode, KeyEvent event);
@Override
public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
return new ActionBarDrawableToggleImpl();
}
final Context getActionBarThemedContext() {
Context context = null;
// If we have an action bar, let it return a themed context
ActionBar ab = getSupportActionBar();
if (ab != null) {
context = ab.getThemedContext();
}
if (context == null) {
context = mContext;
}
return context;
}
private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate {
@Override
public Drawable getThemeUpIndicator() {
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator });
final Drawable result = a.getDrawable(0);
a.recycle();
return result;
}
@Override
public Context getActionBarThemedContext() {
return AppCompatDelegateImplBase.this.getActionBarThemedContext();
}
@Override
public boolean isNavigationVisible() {
final ActionBar ab = getSupportActionBar();
return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
}
@Override
public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setHomeAsUpIndicator(upDrawable);
ab.setHomeActionContentDescription(contentDescRes);
}
}
@Override
public void setActionBarDescription(int contentDescRes) {
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setHomeActionContentDescription(contentDescRes);
}
}
}
abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback);
@Override
public final void onDestroy() {
mIsDestroyed = true;
}
final boolean isDestroyed() {
return mIsDestroyed;
}
final Window.Callback getWindowCallback() {
return mWindow.getCallback();
}
@Override
public final void setTitle(CharSequence title) {
mTitle = title;
onTitleChanged(title);
}
abstract void onTitleChanged(CharSequence title);
final CharSequence getTitle() {
// If the original window callback is an Activity, we'll use it's title
if (mOriginalWindowCallback instanceof Activity) {
return ((Activity) mOriginalWindowCallback).getTitle();
}
// Else, we'll return the title we have recorded ourselves
return mTitle;
}
private class AppCompatWindowCallback extends WindowCallbackWrapper {
AppCompatWindowCallback(Window.Callback callback) {
super(callback);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (AppCompatDelegateImplBase.this.dispatchKeyEvent(event)) {
return true;
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean onCreatePanelMenu(int featureId, Menu menu) {
if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) {
// If this is an options menu but it's not an AppCompat menu, we eat the event
// and return false
return false;
}
return super.onCreatePanelMenu(featureId, menu);
}
@Override
public boolean onPreparePanel(int featureId, View view, Menu menu) {
if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) {
// If this is an options menu but it's not an AppCompat menu, we eat the event
// and return false
return false;
}
if (featureId == Window.FEATURE_OPTIONS_PANEL && bypassPrepareOptionsPanelIfNeeded()) {
// If this is an options menu and we need to bypass onPreparePanel, do so
if (mOriginalWindowCallback instanceof Activity) {
return ((Activity) mOriginalWindowCallback).onPrepareOptionsMenu(menu);
} else if (mOriginalWindowCallback instanceof Dialog) {
return ((Dialog) mOriginalWindowCallback).onPrepareOptionsMenu(menu);
}
return false;
}
// Else, defer to the default handling
return super.onPreparePanel(featureId, view, menu);
}
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
if (AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu)) {
return true;
}
return super.onMenuOpened(featureId, menu);
}
@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
if (AppCompatDelegateImplBase.this.onKeyShortcut(event.getKeyCode(), event)) {
return true;
}
return super.dispatchKeyShortcutEvent(event);
}
@Override
public void onContentChanged() {
// We purposely do not propagate this call as this is called when we install
// our sub-decor rather than the user's content
}
@Override
public void onPanelClosed(int featureId, Menu menu) {
if (AppCompatDelegateImplBase.this.onPanelClosed(featureId, menu)) {
return;
}
super.onPanelClosed(featureId, menu);
}
/**
* For the options menu, we may need to call onPrepareOptionsMenu() directly,
* bypassing onPreparePanel(). This is because onPreparePanel() in certain situations
* calls menu.hasVisibleItems(), which interferes with any initial invisible items.
*
* @return true if onPrepareOptionsMenu should be called directly.
*/
private boolean bypassPrepareOptionsPanelIfNeeded() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN
&& mOriginalWindowCallback instanceof Activity) {
// For Activities, we only need to bypass onPreparePanel if we're running pre-JB
return true;
} else if (mOriginalWindowCallback instanceof Dialog) {
// For Dialogs, we always need to bypass onPreparePanel
return true;
}
return false;
}
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.app;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v7.internal.view.SupportActionModeWrapper;
import android.support.v7.internal.widget.NativeActionModeAwareLayout;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
class AppCompatDelegateImplV11 extends AppCompatDelegateImplV7
implements NativeActionModeAwareLayout.OnActionModeForChildListener {
private NativeActionModeAwareLayout mNativeActionModeAwareLayout;
AppCompatDelegateImplV11(Context context, Window window, AppCompatCallback callback) {
super(context, window, callback);
}
@Override
void onSubDecorInstalled(ViewGroup subDecor) {
// NativeActionModeAwareLayout is used to notify us when a native Action Mode is started
mNativeActionModeAwareLayout = (NativeActionModeAwareLayout)
subDecor.findViewById(android.R.id.content);
// Can be null when using FEATURE_ACTION_BAR_OVERLAY
if (mNativeActionModeAwareLayout != null) {
mNativeActionModeAwareLayout.setActionModeForChildListener(this);
}
}
// From NativeActionModeAwareLayout.OnActionModeForChildListener
@Override
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
Context context = originalView.getContext();
// Try and start a support action mode, wrapping the callback
final android.support.v7.view.ActionMode supportActionMode = startSupportActionMode(
new SupportActionModeWrapper.CallbackWrapper(context, callback));
if (supportActionMode != null) {
// If we received a support action mode, wrap and return it
return new SupportActionModeWrapper(mContext, supportActionMode);
}
return null;
}
View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) {
// First let super have a try, this allows FragmentActivity to inflate any support
// fragments
final View view = super.callActivityOnCreateView(parent, name, context, attrs);
if (view != null) {
return view;
}
// Now, let the Activity's LayoutInflater.Factory2 method try...
if (mOriginalWindowCallback instanceof LayoutInflater.Factory2) {
return ((LayoutInflater.Factory2) mOriginalWindowCallback)
.onCreateView(parent, name, context, attrs);
}
return null;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,163 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v7.app;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v7.appcompat.R;
import android.support.v7.view.ActionMode;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
/**
* Base class for AppCompat themed {@link android.app.Dialog}s.
*/
public class AppCompatDialog extends Dialog implements AppCompatCallback {
private AppCompatDelegate mDelegate;
public AppCompatDialog(Context context) {
this(context, 0);
}
public AppCompatDialog(Context context, int theme) {
super(context, getThemeResId(context, theme));
// This is a bit weird, but Dialog's are typically created and setup before being shown,
// which means that we can't rely on onCreate() being called before a content view is set.
// To workaround this, we call onCreate(null) in the ctor, and then again as usual in
// onCreate().
getDelegate().onCreate(null);
}
protected AppCompatDialog(Context context, boolean cancelable,
OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
super.onCreate(savedInstanceState);
getDelegate().onCreate(savedInstanceState);
}
/**
* Support library version of {@link android.app.Dialog#getActionBar}.
*
* <p>Retrieve a reference to this dialog's ActionBar.
*
* @return The Dialog's ActionBar, or null if it does not have one.
*/
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
getDelegate().setTitle(title);
}
@Override
public void setTitle(int titleId) {
super.setTitle(titleId);
getDelegate().setTitle(getContext().getString(titleId));
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
/**
* Enable extended support library window features.
* <p>
* This is a convenience for calling
* {@link android.view.Window#requestFeature getWindow().requestFeature()}.
* </p>
*
* @param featureId The desired feature as defined in {@link android.view.Window} or
* {@link android.support.v4.view.WindowCompat}.
* @return Returns true if the requested feature is supported and now enabled.
*
* @see android.app.Dialog#requestWindowFeature
* @see android.view.Window#requestFeature
*/
public boolean supportRequestWindowFeature(int featureId) {
return getDelegate().requestWindowFeature(featureId);
}
/**
* @hide
*/
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
/**
* @return The {@link AppCompatDelegate} being used by this Dialog.
*/
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
private static int getThemeResId(Context context, int themeId) {
if (themeId == 0) {
// If the provided theme is 0, then retrieve the dialogTheme from our theme
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeId = outValue.resourceId;
}
return themeId;
}
@Override
public void onSupportActionModeStarted(ActionMode mode) {
}
@Override
public void onSupportActionModeFinished(ActionMode mode) {
}
}

View file

@ -0,0 +1,207 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.app;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.appcompat.R;
/**
* A drawable that can draw a "Drawer hamburger" menu or an Arrow and animate between them.
*/
abstract class DrawerArrowDrawable extends Drawable {
private final Paint mPaint = new Paint();
// The angle in degress that the arrow head is inclined at.
private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);
private final float mBarThickness;
// The length of top and bottom bars when they merge into an arrow
private final float mTopBottomArrowSize;
// The length of middle bar
private final float mBarSize;
// The length of the middle bar when arrow is shaped
private final float mMiddleArrowSize;
// The space between bars when they are parallel
private final float mBarGap;
// Whether bars should spin or not during progress
private final boolean mSpin;
// Use Path instead of canvas operations so that if color has transparency, overlapping sections
// wont look different
private final Path mPath = new Path();
// The reported intrinsic size of the drawable.
private final int mSize;
// Whether we should mirror animation when animation is reversed.
private boolean mVerticalMirror = false;
// The interpolated version of the original progress
private float mProgress;
// the amount that overlaps w/ bar size when rotation is max
private float mMaxCutForBarSize;
// The distance of arrow's center from top when horizontal
private float mCenterOffset;
/**
* @param context used to get the configuration for the drawable from
*/
DrawerArrowDrawable(Context context) {
final TypedArray typedArray = context.getTheme()
.obtainStyledAttributes(null, R.styleable.DrawerArrowToggle,
R.attr.drawerArrowStyle,
R.style.Base_Widget_AppCompat_DrawerArrowToggle);
mPaint.setAntiAlias(true);
mPaint.setColor(typedArray.getColor(R.styleable.DrawerArrowToggle_color, 0));
mSize = typedArray.getDimensionPixelSize(R.styleable.DrawerArrowToggle_drawableSize, 0);
// round this because having this floating may cause bad measurements
mBarSize = Math.round(typedArray.getDimension(R.styleable.DrawerArrowToggle_barSize, 0));
// round this because having this floating may cause bad measurements
mTopBottomArrowSize = Math.round(typedArray.getDimension(
R.styleable.DrawerArrowToggle_topBottomBarArrowSize, 0));
mBarThickness = typedArray.getDimension(R.styleable.DrawerArrowToggle_thickness, 0);
// round this because having this floating may cause bad measurements
mBarGap = Math.round(typedArray.getDimension(
R.styleable.DrawerArrowToggle_gapBetweenBars, 0));
mSpin = typedArray.getBoolean(R.styleable.DrawerArrowToggle_spinBars, true);
mMiddleArrowSize = typedArray
.getDimension(R.styleable.DrawerArrowToggle_middleBarArrowSize, 0);
final int remainingSpace = (int) (mSize - mBarThickness * 3 - mBarGap * 2);
mCenterOffset = (remainingSpace / 4) * 2; //making sure it is a multiple of 2.
mCenterOffset += mBarThickness * 1.5 + mBarGap;
typedArray.recycle();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.MITER);
mPaint.setStrokeCap(Paint.Cap.BUTT);
mPaint.setStrokeWidth(mBarThickness);
mMaxCutForBarSize = (float) (mBarThickness / 2 * Math.cos(ARROW_HEAD_ANGLE));
}
abstract boolean isLayoutRtl();
/**
* If set, canvas is flipped when progress reached to end and going back to start.
*/
protected void setVerticalMirror(boolean verticalMirror) {
mVerticalMirror = verticalMirror;
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
final boolean isRtl = isLayoutRtl();
// Interpolated widths of arrow bars
final float arrowSize = lerp(mBarSize, mTopBottomArrowSize, mProgress);
final float middleBarSize = lerp(mBarSize, mMiddleArrowSize, mProgress);
// Interpolated size of middle bar
final float middleBarCut = Math.round(lerp(0, mMaxCutForBarSize, mProgress));
// The rotation of the top and bottom bars (that make the arrow head)
final float rotation = lerp(0, ARROW_HEAD_ANGLE, mProgress);
// The whole canvas rotates as the transition happens
final float canvasRotate = lerp(isRtl ? 0 : -180, isRtl ? 180 : 0, mProgress);
final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
mPath.rewind();
final float topBottomBarOffset = lerp(mBarGap + mBarThickness, -mMaxCutForBarSize,
mProgress);
final float arrowEdge = -middleBarSize / 2;
// draw middle bar
mPath.moveTo(arrowEdge + middleBarCut, 0);
mPath.rLineTo(middleBarSize - middleBarCut * 2, 0);
// bottom bar
mPath.moveTo(arrowEdge, topBottomBarOffset);
mPath.rLineTo(arrowWidth, arrowHeight);
// top bar
mPath.moveTo(arrowEdge, -topBottomBarOffset);
mPath.rLineTo(arrowWidth, -arrowHeight);
mPath.close();
canvas.save();
// Rotate the whole canvas if spinning, if not, rotate it 180 to get
// the arrow pointing the other way for RTL.
canvas.translate(bounds.centerX(), mCenterOffset);
if (mSpin) {
canvas.rotate(canvasRotate * ((mVerticalMirror ^ isRtl) ? -1 : 1));
} else if (isRtl) {
canvas.rotate(180);
}
canvas.drawPath(mPath, mPaint);
canvas.restore();
}
@Override
public void setAlpha(int i) {
mPaint.setAlpha(i);
}
// override
public boolean isAutoMirrored() {
// Draws rotated 180 degrees in RTL mode.
return true;
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getIntrinsicHeight() {
return mSize;
}
@Override
public int getIntrinsicWidth() {
return mSize;
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
public float getProgress() {
return mProgress;
}
public void setProgress(float progress) {
mProgress = progress;
invalidateSelf();
}
/**
* Linear interpolate between a and b with parameter t.
*/
private static float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
}

View file

@ -0,0 +1,225 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.graphics.drawable;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.view.View;
/**
* Drawable which delegates all calls to it's wrapped {@link Drawable}.
* <p>
* The wrapped {@link Drawable} <em>must</em> be fully released from any {@link View}
* before wrapping, otherwise internal {@link Drawable.Callback} may be dropped.
*
* @hide
*/
public class DrawableWrapper extends Drawable implements Drawable.Callback {
private Drawable mDrawable;
public DrawableWrapper(Drawable drawable) {
setWrappedDrawable(drawable);
}
@Override
public void draw(Canvas canvas) {
mDrawable.draw(canvas);
}
@Override
protected void onBoundsChange(Rect bounds) {
mDrawable.setBounds(bounds);
}
@Override
public void setChangingConfigurations(int configs) {
mDrawable.setChangingConfigurations(configs);
}
@Override
public int getChangingConfigurations() {
return mDrawable.getChangingConfigurations();
}
@Override
public void setDither(boolean dither) {
mDrawable.setDither(dither);
}
@Override
public void setFilterBitmap(boolean filter) {
mDrawable.setFilterBitmap(filter);
}
@Override
public void setAlpha(int alpha) {
mDrawable.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mDrawable.setColorFilter(cf);
}
@Override
public boolean isStateful() {
return mDrawable.isStateful();
}
@Override
public boolean setState(final int[] stateSet) {
return mDrawable.setState(stateSet);
}
@Override
public int[] getState() {
return mDrawable.getState();
}
public void jumpToCurrentState() {
DrawableCompat.jumpToCurrentState(mDrawable);
}
@Override
public Drawable getCurrent() {
return mDrawable.getCurrent();
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
return super.setVisible(visible, restart) || mDrawable.setVisible(visible, restart);
}
@Override
public int getOpacity() {
return mDrawable.getOpacity();
}
@Override
public Region getTransparentRegion() {
return mDrawable.getTransparentRegion();
}
@Override
public int getIntrinsicWidth() {
return mDrawable.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
return mDrawable.getIntrinsicHeight();
}
@Override
public int getMinimumWidth() {
return mDrawable.getMinimumWidth();
}
@Override
public int getMinimumHeight() {
return mDrawable.getMinimumHeight();
}
@Override
public boolean getPadding(Rect padding) {
return mDrawable.getPadding(padding);
}
/**
* {@inheritDoc}
*/
public void invalidateDrawable(Drawable who) {
invalidateSelf();
}
/**
* {@inheritDoc}
*/
public void scheduleDrawable(Drawable who, Runnable what, long when) {
scheduleSelf(what, when);
}
/**
* {@inheritDoc}
*/
public void unscheduleDrawable(Drawable who, Runnable what) {
unscheduleSelf(what);
}
@Override
protected boolean onLevelChange(int level) {
return mDrawable.setLevel(level);
}
@Override
public void setAutoMirrored(boolean mirrored) {
DrawableCompat.setAutoMirrored(mDrawable, mirrored);
}
@Override
public boolean isAutoMirrored() {
return DrawableCompat.isAutoMirrored(mDrawable);
}
@Override
public void setTint(int tint) {
DrawableCompat.setTint(mDrawable, tint);
}
@Override
public void setTintList(ColorStateList tint) {
DrawableCompat.setTintList(mDrawable, tint);
}
@Override
public void setTintMode(PorterDuff.Mode tintMode) {
DrawableCompat.setTintMode(mDrawable, tintMode);
}
@Override
public void setHotspot(float x, float y) {
DrawableCompat.setHotspot(mDrawable, x, y);
}
@Override
public void setHotspotBounds(int left, int top, int right, int bottom) {
DrawableCompat.setHotspotBounds(mDrawable, left, top, right, bottom);
}
public Drawable getWrappedDrawable() {
return mDrawable;
}
public void setWrappedDrawable(Drawable drawable) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
mDrawable = drawable;
if (drawable != null) {
drawable.setCallback(this);
}
}
}

View file

@ -0,0 +1,16 @@
package android.support.v7.internal;
import android.os.Build;
/**
* @hide
*/
public class VersionUtils {
private VersionUtils() {}
public static boolean isAtLeastL() {
return Build.VERSION.SDK_INT >= 21;
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.app;
import android.support.v7.app.ActionBar;
import android.support.v7.internal.widget.AdapterViewCompat;
import android.view.View;
/**
* Wrapper to adapt the ActionBar.OnNavigationListener in an AdapterView.OnItemSelectedListener
* for use in Spinner widgets. Used by action bar implementations.
*
* @hide
*/
class NavItemSelectedListener implements AdapterViewCompat.OnItemSelectedListener {
private final ActionBar.OnNavigationListener mListener;
public NavItemSelectedListener(ActionBar.OnNavigationListener listener) {
mListener = listener;
}
@Override
public void onItemSelected(AdapterViewCompat<?> parent, View view, int position, long id) {
if (mListener != null) {
mListener.onNavigationItemSelected(position, id);
}
}
@Override
public void onNothingSelected(AdapterViewCompat<?> parent) {
// Do nothing
}
}

View file

@ -0,0 +1,156 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.app;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.internal.widget.TintAutoCompleteTextView;
import android.support.v7.internal.widget.TintButton;
import android.support.v7.internal.widget.TintCheckBox;
import android.support.v7.internal.widget.TintCheckedTextView;
import android.support.v7.internal.widget.TintEditText;
import android.support.v7.internal.widget.TintMultiAutoCompleteTextView;
import android.support.v7.internal.widget.TintRadioButton;
import android.support.v7.internal.widget.TintRatingBar;
import android.support.v7.internal.widget.TintSpinner;
import android.support.v7.internal.widget.ViewUtils;
import android.util.AttributeSet;
import android.view.InflateException;
import android.view.View;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
/**
* This class is responsible for manually inflating our tinted widgets which are used on devices
* running {@link android.os.Build.VERSION_CODES#KITKAT KITKAT} or below. As such, this class
* should only be used when running on those devices.
* <p>This class two main responsibilities: the first is to 'inject' our tinted views in place of
* the framework versions in layout inflation; the second is backport the {@code android:theme}
* functionality for any inflated widgets. This include theme inheritance from it's parent.
*
* @hide
*/
public class TintViewInflater {
static final Class<?>[] sConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
private static final Map<String, Constructor<? extends View>> sConstructorMap = new HashMap<>();
private final Context mContext;
private final Object[] mConstructorArgs = new Object[2];
public TintViewInflater(Context context) {
mContext = context;
}
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext, boolean themeContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (themeContext) {
// We then apply the theme on the context, if specified
context = ViewUtils.themifyContext(context, attrs, true, true);
}
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "EditText":
return new TintEditText(context, attrs);
case "Spinner":
return new TintSpinner(context, attrs);
case "CheckBox":
return new TintCheckBox(context, attrs);
case "RadioButton":
return new TintRadioButton(context, attrs);
case "CheckedTextView":
return new TintCheckedTextView(context, attrs);
case "AutoCompleteTextView":
return new TintAutoCompleteTextView(context, attrs);
case "MultiAutoCompleteTextView":
return new TintMultiAutoCompleteTextView(context, attrs);
case "RatingBar":
return new TintRatingBar(context, attrs);
case "Button":
return new TintButton(context, attrs);
}
if (originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that app:theme takes effect.
return createViewFromTag(context, name, attrs);
}
return null;
}
private View createViewFromTag(Context context, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
try {
mConstructorArgs[0] = context;
mConstructorArgs[1] = attrs;
if (-1 == name.indexOf('.')) {
// try the android.widget prefix first...
return createView(name, "android.widget.");
} else {
return createView(name, null);
}
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = null;
mConstructorArgs[1] = null;
}
}
private View createView(String name, String prefix)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
Class<? extends View> clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
constructor = clazz.getConstructor(sConstructorSignature);
sConstructorMap.put(name, constructor);
}
constructor.setAccessible(true);
return constructor.newInstance(mConstructorArgs);
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
}
}
}

View file

@ -0,0 +1,635 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.app;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.internal.view.WindowCallbackWrapper;
import android.support.v7.appcompat.R;
import android.support.v7.internal.view.menu.ListMenuPresenter;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.view.menu.MenuPresenter;
import android.support.v7.internal.widget.DecorToolbar;
import android.support.v7.internal.widget.ToolbarWidgetWrapper;
import android.support.v7.widget.Toolbar;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.SpinnerAdapter;
import java.util.ArrayList;
/**
* @hide
*/
public class ToolbarActionBar extends ActionBar {
private DecorToolbar mDecorToolbar;
private boolean mToolbarMenuPrepared;
private Window.Callback mWindowCallback;
private boolean mMenuCallbackSet;
private boolean mLastMenuVisibility;
private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
new ArrayList<OnMenuVisibilityListener>();
private Window mWindow;
private ListMenuPresenter mListMenuPresenter;
private final Runnable mMenuInvalidator = new Runnable() {
@Override
public void run() {
populateOptionsMenu();
}
};
private final Toolbar.OnMenuItemClickListener mMenuClicker =
new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
}
};
public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window window) {
mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
mWindowCallback = new ToolbarCallbackWrapper(window.getCallback());
mDecorToolbar.setWindowCallback(mWindowCallback);
toolbar.setOnMenuItemClickListener(mMenuClicker);
mDecorToolbar.setWindowTitle(title);
mWindow = window;
}
public Window.Callback getWrappedWindowCallback() {
return mWindowCallback;
}
@Override
public void setCustomView(View view) {
setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
@Override
public void setCustomView(View view, LayoutParams layoutParams) {
view.setLayoutParams(layoutParams);
mDecorToolbar.setCustomView(view);
}
@Override
public void setCustomView(int resId) {
final LayoutInflater inflater = LayoutInflater.from(mDecorToolbar.getContext());
setCustomView(inflater.inflate(resId, mDecorToolbar.getViewGroup(), false));
}
@Override
public void setIcon(int resId) {
mDecorToolbar.setIcon(resId);
}
@Override
public void setIcon(Drawable icon) {
mDecorToolbar.setIcon(icon);
}
@Override
public void setLogo(int resId) {
mDecorToolbar.setLogo(resId);
}
@Override
public void setLogo(Drawable logo) {
mDecorToolbar.setLogo(logo);
}
@Override
public void setStackedBackgroundDrawable(Drawable d) {
// This space for rent (do nothing)
}
@Override
public void setSplitBackgroundDrawable(Drawable d) {
// This space for rent (do nothing)
}
@Override
public void setHomeButtonEnabled(boolean enabled) {
// If the nav button on a Toolbar is present, it's enabled. No-op.
}
@Override
public void setElevation(float elevation) {
ViewCompat.setElevation(mDecorToolbar.getViewGroup(), elevation);
}
@Override
public float getElevation() {
return ViewCompat.getElevation(mDecorToolbar.getViewGroup());
}
@Override
public Context getThemedContext() {
return mDecorToolbar.getContext();
}
@Override
public boolean isTitleTruncated() {
return super.isTitleTruncated();
}
@Override
public void setHomeAsUpIndicator(Drawable indicator) {
mDecorToolbar.setNavigationIcon(indicator);
}
@Override
public void setHomeAsUpIndicator(int resId) {
mDecorToolbar.setNavigationIcon(resId);
}
@Override
public void setHomeActionContentDescription(CharSequence description) {
mDecorToolbar.setNavigationContentDescription(description);
}
@Override
public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
// Do nothing
}
@Override
public void setHomeActionContentDescription(int resId) {
mDecorToolbar.setNavigationContentDescription(resId);
}
@Override
public void setShowHideAnimationEnabled(boolean enabled) {
// This space for rent; no-op.
}
@Override
public void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
}
@Override
public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback));
}
@Override
public void setSelectedNavigationItem(int position) {
switch (mDecorToolbar.getNavigationMode()) {
case NAVIGATION_MODE_LIST:
mDecorToolbar.setDropdownSelectedPosition(position);
break;
default:
throw new IllegalStateException(
"setSelectedNavigationIndex not valid for current navigation mode");
}
}
@Override
public int getSelectedNavigationIndex() {
return -1;
}
@Override
public int getNavigationItemCount() {
return 0;
}
@Override
public void setTitle(CharSequence title) {
mDecorToolbar.setTitle(title);
}
@Override
public void setTitle(int resId) {
mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
}
@Override
public void setWindowTitle(CharSequence title) {
mDecorToolbar.setWindowTitle(title);
}
@Override
public void setSubtitle(CharSequence subtitle) {
mDecorToolbar.setSubtitle(subtitle);
}
@Override
public void setSubtitle(int resId) {
mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
}
@Override
public void setDisplayOptions(@DisplayOptions int options) {
setDisplayOptions(options, 0xffffffff);
}
@Override
public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {
final int currentOptions = mDecorToolbar.getDisplayOptions();
mDecorToolbar.setDisplayOptions(options & mask | currentOptions & ~mask);
}
@Override
public void setDisplayUseLogoEnabled(boolean useLogo) {
setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO);
}
@Override
public void setDisplayShowHomeEnabled(boolean showHome) {
setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME);
}
@Override
public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP);
}
@Override
public void setDisplayShowTitleEnabled(boolean showTitle) {
setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE);
}
@Override
public void setDisplayShowCustomEnabled(boolean showCustom) {
setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM);
}
@Override
public void setBackgroundDrawable(@Nullable Drawable d) {
mDecorToolbar.setBackgroundDrawable(d);
}
@Override
public View getCustomView() {
return mDecorToolbar.getCustomView();
}
@Override
public CharSequence getTitle() {
return mDecorToolbar.getTitle();
}
@Override
public CharSequence getSubtitle() {
return mDecorToolbar.getSubtitle();
}
@Override
public int getNavigationMode() {
return NAVIGATION_MODE_STANDARD;
}
@Override
public void setNavigationMode(@NavigationMode int mode) {
if (mode == ActionBar.NAVIGATION_MODE_TABS) {
throw new IllegalArgumentException("Tabs not supported in this configuration");
}
mDecorToolbar.setNavigationMode(mode);
}
@Override
public int getDisplayOptions() {
return mDecorToolbar.getDisplayOptions();
}
@Override
public Tab newTab() {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public void addTab(Tab tab) {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public void addTab(Tab tab, boolean setSelected) {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public void addTab(Tab tab, int position) {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public void addTab(Tab tab, int position, boolean setSelected) {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public void removeTab(Tab tab) {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public void removeTabAt(int position) {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public void removeAllTabs() {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public void selectTab(Tab tab) {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public Tab getSelectedTab() {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public Tab getTabAt(int index) {
throw new UnsupportedOperationException(
"Tabs are not supported in toolbar action bars");
}
@Override
public int getTabCount() {
return 0;
}
@Override
public int getHeight() {
return mDecorToolbar.getHeight();
}
@Override
public void show() {
// TODO: Consider a better transition for this.
// Right now use no automatic transition so that the app can supply one if desired.
mDecorToolbar.setVisibility(View.VISIBLE);
}
@Override
public void hide() {
// TODO: Consider a better transition for this.
// Right now use no automatic transition so that the app can supply one if desired.
mDecorToolbar.setVisibility(View.GONE);
}
@Override
public boolean isShowing() {
return mDecorToolbar.getVisibility() == View.VISIBLE;
}
@Override
public boolean openOptionsMenu() {
return mDecorToolbar.showOverflowMenu();
}
@Override
public boolean invalidateOptionsMenu() {
mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
ViewCompat.postOnAnimation(mDecorToolbar.getViewGroup(), mMenuInvalidator);
return true;
}
@Override
public boolean collapseActionView() {
if (mDecorToolbar.hasExpandedActionView()) {
mDecorToolbar.collapseActionView();
return true;
}
return false;
}
void populateOptionsMenu() {
final Menu menu = getMenu();
final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null;
if (mb != null) {
mb.stopDispatchingItemsChanged();
}
try {
menu.clear();
if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) ||
!mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) {
menu.clear();
}
} finally {
if (mb != null) {
mb.startDispatchingItemsChanged();
}
}
}
@Override
public boolean onMenuKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP) {
openOptionsMenu();
}
return true;
}
@Override
public boolean onKeyShortcut(int keyCode, KeyEvent ev) {
Menu menu = getMenu();
return menu != null ? menu.performShortcut(keyCode, ev, 0) : false;
}
public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
mMenuVisibilityListeners.add(listener);
}
public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
mMenuVisibilityListeners.remove(listener);
}
public void dispatchMenuVisibilityChanged(boolean isVisible) {
if (isVisible == mLastMenuVisibility) {
return;
}
mLastMenuVisibility = isVisible;
final int count = mMenuVisibilityListeners.size();
for (int i = 0; i < count; i++) {
mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible);
}
}
private View getListMenuView(Menu menu) {
ensureListMenuPresenter(menu);
if (menu == null || mListMenuPresenter == null) {
return null;
}
if (mListMenuPresenter.getAdapter().getCount() > 0) {
return (View) mListMenuPresenter.getMenuView(mDecorToolbar.getViewGroup());
}
return null;
}
private void ensureListMenuPresenter(Menu menu) {
if (mListMenuPresenter == null && (menu instanceof MenuBuilder)) {
MenuBuilder mb = (MenuBuilder) menu;
Context context = mDecorToolbar.getContext();
final TypedValue outValue = new TypedValue();
final Resources.Theme widgetTheme = context.getResources().newTheme();
widgetTheme.setTo(context.getTheme());
// Apply the panelMenuListTheme
widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true);
if (outValue.resourceId != 0) {
widgetTheme.applyStyle(outValue.resourceId, true);
} else {
widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true);
}
context = new ContextThemeWrapper(context, 0);
context.getTheme().setTo(widgetTheme);
// Finally create the list menu presenter
mListMenuPresenter = new ListMenuPresenter(context, R.layout.abc_list_menu_item_layout);
mListMenuPresenter.setCallback(new PanelMenuPresenterCallback());
mb.addMenuPresenter(mListMenuPresenter);
}
}
private class ToolbarCallbackWrapper extends WindowCallbackWrapper {
public ToolbarCallbackWrapper(Window.Callback wrapped) {
super(wrapped);
}
@Override
public boolean onPreparePanel(int featureId, View view, Menu menu) {
final boolean result = super.onPreparePanel(featureId, view, menu);
if (result && !mToolbarMenuPrepared) {
mDecorToolbar.setMenuPrepared();
mToolbarMenuPrepared = true;
}
return result;
}
@Override
public View onCreatePanelView(int featureId) {
switch (featureId) {
case Window.FEATURE_OPTIONS_PANEL:
final Menu menu = mDecorToolbar.getMenu();
if (onPreparePanel(featureId, null, menu) && onMenuOpened(featureId, menu)) {
return getListMenuView(menu);
}
break;
}
return super.onCreatePanelView(featureId);
}
}
private Menu getMenu() {
if (!mMenuCallbackSet) {
mDecorToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(),
new MenuBuilderCallback());
mMenuCallbackSet = true;
}
return mDecorToolbar.getMenu();
}
private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
private boolean mClosingActionMenu;
@Override
public boolean onOpenSubMenu(MenuBuilder subMenu) {
if (mWindowCallback != null) {
mWindowCallback.onMenuOpened(WindowCompat.FEATURE_ACTION_BAR, subMenu);
return true;
}
return false;
}
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
if (mClosingActionMenu) {
return;
}
mClosingActionMenu = true;
mDecorToolbar.dismissPopupMenus();
if (mWindowCallback != null) {
mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu);
}
mClosingActionMenu = false;
}
}
private final class PanelMenuPresenterCallback implements MenuPresenter.Callback {
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
if (mWindowCallback != null) {
mWindowCallback.onPanelClosed(Window.FEATURE_OPTIONS_PANEL, menu);
}
}
@Override
public boolean onOpenSubMenu(MenuBuilder subMenu) {
if (subMenu == null && mWindowCallback != null) {
mWindowCallback.onMenuOpened(Window.FEATURE_OPTIONS_PANEL, subMenu);
}
return true;
}
}
private final class MenuBuilderCallback implements MenuBuilder.Callback {
@Override
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
return false;
}
@Override
public void onMenuModeChange(MenuBuilder menu) {
if (mWindowCallback != null) {
if (mDecorToolbar.isOverflowMenuShowing()) {
mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu);
} else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL,
null, menu)) {
mWindowCallback.onMenuOpened(WindowCompat.FEATURE_ACTION_BAR, menu);
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.text;
import android.content.Context;
import android.graphics.Rect;
import android.text.method.TransformationMethod;
import android.view.View;
import java.util.Locale;
/**
* @hide
*/
public class AllCapsTransformationMethod implements TransformationMethod {
private Locale mLocale;
public AllCapsTransformationMethod(Context context) {
mLocale = context.getResources().getConfiguration().locale;
}
@Override
public CharSequence getTransformation(CharSequence source, View view) {
return source != null ? source.toString().toUpperCase(mLocale) : null;
}
@Override
public void onFocusChanged(View view, CharSequence sourceText, boolean focused,
int direction, Rect previouslyFocusedRect) {
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.transition;
import android.view.ViewGroup;
/**
* @hide
*/
public class ActionBarTransition {
private static final boolean TRANSITIONS_ENABLED = false;
private static final int TRANSITION_DURATION = 120; // ms
// private static final Transition sTransition;
//
// static {
// if (TRANSITIONS_ENABLED) {
//// final ChangeText tc = new ChangeText();
//// tc.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN);
// final TransitionSet inner = new TransitionSet();
// inner.addTransition(new ChangeBounds());
// final TransitionSet tg = new TransitionSet();
// tg.addTransition(new Fade(Fade.OUT)).addTransition(inner).
// addTransition(new Fade(Fade.IN));
// tg.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
// tg.setDuration(TRANSITION_DURATION);
// sTransition = tg;
// } else {
// sTransition = null;
// }
// }
public static void beginDelayedTransition(ViewGroup sceneRoot) {
// if (TRANSITIONS_ENABLED) {
// TransitionManager.beginDelayedTransition(sceneRoot, sTransition);
// }
}
}

View file

@ -0,0 +1,97 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 android.support.v7.internal.view;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v7.appcompat.R;
import android.view.ViewConfiguration;
/**
* Allows components to query for various configuration policy decisions about how the action bar
* should lay out and behave on the current device.
*
* @hide
*/
public class ActionBarPolicy {
private Context mContext;
public static ActionBarPolicy get(Context context) {
return new ActionBarPolicy(context);
}
private ActionBarPolicy(Context context) {
mContext = context;
}
public int getMaxActionButtons() {
return mContext.getResources().getInteger(R.integer.abc_max_action_buttons);
}
public boolean showsOverflowMenuButton() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return true;
} else {
return !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext));
}
}
public int getEmbeddedMenuWidthLimit() {
return mContext.getResources().getDisplayMetrics().widthPixels / 2;
}
public boolean hasEmbeddedTabs() {
final int targetSdk = mContext.getApplicationInfo().targetSdkVersion;
if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs);
}
// The embedded tabs policy changed in Jellybean; give older apps the old policy
// so they get what they expect.
return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs_pre_jb);
}
public int getTabContainerHeight() {
TypedArray a = mContext.obtainStyledAttributes(null, R.styleable.ActionBar,
R.attr.actionBarStyle, 0);
int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
Resources r = mContext.getResources();
if (!hasEmbeddedTabs()) {
// Stacked tabs; limit the height
height = Math.min(height,
r.getDimensionPixelSize(R.dimen.abc_action_bar_stacked_max_height));
}
a.recycle();
return height;
}
public boolean enableHomeButtonByDefault() {
// Older apps get the home button interaction enabled by default.
// Newer apps need to enable it explicitly.
return mContext.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.ICE_CREAM_SANDWICH;
}
public int getStackedTabMaxWidth() {
return mContext.getResources().getDimensionPixelSize(
R.dimen.abc_action_bar_stacked_tab_max_width);
}
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.view;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.support.v7.appcompat.R;
import android.view.LayoutInflater;
/**
* A ContextWrapper that allows you to modify the theme from what is in the
* wrapped context.
*
* @hide
*/
public class ContextThemeWrapper extends ContextWrapper {
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
public ContextThemeWrapper(Context base, int themeres) {
super(base);
mThemeResource = themeres;
}
@Override
public void setTheme(int resid) {
mThemeResource = resid;
initializeTheme();
}
public int getThemeResId() {
return mThemeResource;
}
@Override
public Resources.Theme getTheme() {
if (mTheme != null) {
return mTheme;
}
if (mThemeResource == 0) {
mThemeResource = R.style.Theme_AppCompat_Light;
}
initializeTheme();
return mTheme;
}
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
/**
* Called by {@link #setTheme} and {@link #getTheme} to apply a theme
* resource to the current Theme object. Can override to change the
* default (simple) behavior. This method will not be called in multiple
* threads simultaneously.
*
* @param theme The Theme object being modified.
* @param resid The theme style resource being applied to <var>theme</var>.
* @param first Set to true if this is the first time a style is being
* applied to <var>theme</var>.
*/
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
theme.applyStyle(resid, true);
}
private void initializeTheme() {
final boolean first = mTheme == null;
if (first) {
mTheme = getResources().newTheme();
Resources.Theme theme = getBaseContext().getTheme();
if (theme != null) {
mTheme.setTo(theme);
}
}
onApplyThemeResource(mTheme, mThemeResource, first);
}
}

View file

@ -0,0 +1,163 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.view.menu.MenuPopupHelper;
import android.support.v7.internal.view.menu.SubMenuBuilder;
import android.support.v7.internal.widget.ActionBarContextView;
import android.support.v7.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import java.lang.ref.WeakReference;
/**
* @hide
*/
public class StandaloneActionMode extends ActionMode implements MenuBuilder.Callback {
private Context mContext;
private ActionBarContextView mContextView;
private ActionMode.Callback mCallback;
private WeakReference<View> mCustomView;
private boolean mFinished;
private boolean mFocusable;
private MenuBuilder mMenu;
public StandaloneActionMode(Context context, ActionBarContextView view,
ActionMode.Callback callback, boolean isFocusable) {
mContext = context;
mContextView = view;
mCallback = callback;
mMenu = new MenuBuilder(context).setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
mMenu.setCallback(this);
mFocusable = isFocusable;
}
@Override
public void setTitle(CharSequence title) {
mContextView.setTitle(title);
}
@Override
public void setSubtitle(CharSequence subtitle) {
mContextView.setSubtitle(subtitle);
}
@Override
public void setTitle(int resId) {
setTitle(mContext.getString(resId));
}
@Override
public void setSubtitle(int resId) {
setSubtitle(mContext.getString(resId));
}
@Override
public void setTitleOptionalHint(boolean titleOptional) {
super.setTitleOptionalHint(titleOptional);
mContextView.setTitleOptional(titleOptional);
}
@Override
public boolean isTitleOptional() {
return mContextView.isTitleOptional();
}
@Override
public void setCustomView(View view) {
mContextView.setCustomView(view);
mCustomView = view != null ? new WeakReference<View>(view) : null;
}
@Override
public void invalidate() {
mCallback.onPrepareActionMode(this, mMenu);
}
@Override
public void finish() {
if (mFinished) {
return;
}
mFinished = true;
mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
mCallback.onDestroyActionMode(this);
}
@Override
public Menu getMenu() {
return mMenu;
}
@Override
public CharSequence getTitle() {
return mContextView.getTitle();
}
@Override
public CharSequence getSubtitle() {
return mContextView.getSubtitle();
}
@Override
public View getCustomView() {
return mCustomView != null ? mCustomView.get() : null;
}
@Override
public MenuInflater getMenuInflater() {
return new MenuInflater(mContext);
}
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
return mCallback.onActionItemClicked(this, item);
}
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
}
public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
if (!subMenu.hasVisibleItems()) {
return true;
}
new MenuPopupHelper(mContext, subMenu).show();
return true;
}
public void onCloseSubMenu(SubMenuBuilder menu) {
}
public void onMenuModeChange(MenuBuilder menu) {
invalidate();
mContextView.showOverflowMenu();
}
public boolean isUiFocusable() {
return mFocusable;
}
}

View file

@ -0,0 +1,199 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.internal.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v4.internal.view.SupportMenu;
import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.util.SimpleArrayMap;
import android.support.v7.internal.view.menu.MenuWrapperFactory;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
/**
* Wraps a support {@link android.support.v7.view.ActionMode} as a framework
* {@link android.view.ActionMode}.
*
* @hide
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class SupportActionModeWrapper extends ActionMode {
final Context mContext;
final android.support.v7.view.ActionMode mWrappedObject;
public SupportActionModeWrapper(Context context,
android.support.v7.view.ActionMode supportActionMode) {
mContext = context;
mWrappedObject = supportActionMode;
}
@Override
public Object getTag() {
return mWrappedObject.getTag();
}
@Override
public void setTag(Object tag) {
mWrappedObject.setTag(tag);
}
@Override
public void setTitle(CharSequence title) {
mWrappedObject.setTitle(title);
}
@Override
public void setSubtitle(CharSequence subtitle) {
mWrappedObject.setSubtitle(subtitle);
}
@Override
public void invalidate() {
mWrappedObject.invalidate();
}
@Override
public void finish() {
mWrappedObject.finish();
}
@Override
public Menu getMenu() {
return MenuWrapperFactory.wrapSupportMenu(mContext, (SupportMenu) mWrappedObject.getMenu());
}
@Override
public CharSequence getTitle() {
return mWrappedObject.getTitle();
}
@Override
public void setTitle(int resId) {
mWrappedObject.setTitle(resId);
}
@Override
public CharSequence getSubtitle() {
return mWrappedObject.getSubtitle();
}
@Override
public void setSubtitle(int resId) {
mWrappedObject.setSubtitle(resId);
}
@Override
public View getCustomView() {
return mWrappedObject.getCustomView();
}
@Override
public void setCustomView(View view) {
mWrappedObject.setCustomView(view);
}
@Override
public MenuInflater getMenuInflater() {
return mWrappedObject.getMenuInflater();
}
@Override
public boolean getTitleOptionalHint() {
return mWrappedObject.getTitleOptionalHint();
}
@Override
public void setTitleOptionalHint(boolean titleOptional) {
mWrappedObject.setTitleOptionalHint(titleOptional);
}
@Override
public boolean isTitleOptional() {
return mWrappedObject.isTitleOptional();
}
/**
* @hide
*/
public static class CallbackWrapper implements android.support.v7.view.ActionMode.Callback {
final Callback mWrappedCallback;
final Context mContext;
final SimpleArrayMap<android.support.v7.view.ActionMode, SupportActionModeWrapper>
mActionModes;
final SimpleArrayMap<Menu, Menu> mMenus;
public CallbackWrapper(Context context, Callback supportCallback) {
mContext = context;
mWrappedCallback = supportCallback;
mActionModes = new SimpleArrayMap<>();
mMenus = new SimpleArrayMap<>();
}
@Override
public boolean onCreateActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
return mWrappedCallback.onCreateActionMode(getActionModeWrapper(mode),
getMenuWrapper(menu));
}
@Override
public boolean onPrepareActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
return mWrappedCallback.onPrepareActionMode(getActionModeWrapper(mode),
getMenuWrapper(menu));
}
@Override
public boolean onActionItemClicked(android.support.v7.view.ActionMode mode,
android.view.MenuItem item) {
return mWrappedCallback.onActionItemClicked(getActionModeWrapper(mode),
MenuWrapperFactory.wrapSupportMenuItem(mContext, (SupportMenuItem) item));
}
@Override
public void onDestroyActionMode(android.support.v7.view.ActionMode mode) {
mWrappedCallback.onDestroyActionMode(getActionModeWrapper(mode));
}
private Menu getMenuWrapper(Menu menu) {
Menu wrapper = mMenus.get(menu);
if (wrapper == null) {
wrapper = MenuWrapperFactory.wrapSupportMenu(mContext, (SupportMenu) menu);
mMenus.put(menu, wrapper);
}
return wrapper;
}
private ActionMode getActionModeWrapper(android.support.v7.view.ActionMode mode) {
// First see if we already have a wrapper for this mode
SupportActionModeWrapper wrapper = mActionModes.get(mode);
if (wrapper != null) {
return wrapper;
}
// If we reach here then we haven't seen this mode before. Create a new wrapper and
// add it to our collection
wrapper = new SupportActionModeWrapper(mContext, mode);
mActionModes.put(mode, wrapper);
return wrapper;
}
}
}

View file

@ -0,0 +1,506 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.internal.view;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.support.v4.internal.view.SupportMenu;
import android.support.v4.view.ActionProvider;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.appcompat.R;
import android.support.v7.internal.view.menu.MenuItemImpl;
import android.support.v7.internal.view.menu.MenuItemWrapperICS;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import android.view.InflateException;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* This class is used to instantiate menu XML files into Menu objects.
* <p>
* For performance reasons, menu inflation relies heavily on pre-processing of
* XML files that is done at build time. Therefore, it is not currently possible
* to use SupportMenuInflater with an XmlPullParser over a plain XML file at runtime;
* it only works with an XmlPullParser returned from a compiled resource (R.
* <em>something</em> file.)
*
* @hide
*/
public class SupportMenuInflater extends MenuInflater {
private static final String LOG_TAG = "SupportMenuInflater";
/** Menu tag name in XML. */
private static final String XML_MENU = "menu";
/** Group tag name in XML. */
private static final String XML_GROUP = "group";
/** Item tag name in XML. */
private static final String XML_ITEM = "item";
private static final int NO_ID = 0;
private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class};
private static final Class<?>[] ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE =
ACTION_VIEW_CONSTRUCTOR_SIGNATURE;
private final Object[] mActionViewConstructorArguments;
private final Object[] mActionProviderConstructorArguments;
private Context mContext;
private Object mRealOwner;
/**
* Constructs a menu inflater.
*
* @see Activity#getMenuInflater()
*/
public SupportMenuInflater(Context context) {
super(context);
mContext = context;
mActionViewConstructorArguments = new Object[] {context};
mActionProviderConstructorArguments = mActionViewConstructorArguments;
}
/**
* Inflate a menu hierarchy from the specified XML resource. Throws
* {@link InflateException} if there is an error.
*
* @param menuRes Resource ID for an XML layout resource to load (e.g.,
* <code>R.menu.main_activity</code>)
* @param menu The Menu to inflate into. The items and submenus will be
* added to this Menu.
*/
@Override
public void inflate(int menuRes, Menu menu) {
// If we're not dealing with a SupportMenu instance, let super handle
if (!(menu instanceof SupportMenu)) {
super.inflate(menuRes, menu);
return;
}
XmlResourceParser parser = null;
try {
parser = mContext.getResources().getLayout(menuRes);
AttributeSet attrs = Xml.asAttributeSet(parser);
parseMenu(parser, attrs, menu);
} catch (XmlPullParserException e) {
throw new InflateException("Error inflating menu XML", e);
} catch (IOException e) {
throw new InflateException("Error inflating menu XML", e);
} finally {
if (parser != null) parser.close();
}
}
/**
* Called internally to fill the given menu. If a sub menu is seen, it will
* call this recursively.
*/
private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)
throws XmlPullParserException, IOException {
MenuState menuState = new MenuState(menu);
int eventType = parser.getEventType();
String tagName;
boolean lookingForEndOfUnknownTag = false;
String unknownTagName = null;
// This loop will skip to the menu start tag
do {
if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
if (tagName.equals(XML_MENU)) {
// Go to next tag
eventType = parser.next();
break;
}
throw new RuntimeException("Expecting menu, got " + tagName);
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
boolean reachedEndOfMenu = false;
while (!reachedEndOfMenu) {
switch (eventType) {
case XmlPullParser.START_TAG:
if (lookingForEndOfUnknownTag) {
break;
}
tagName = parser.getName();
if (tagName.equals(XML_GROUP)) {
menuState.readGroup(attrs);
} else if (tagName.equals(XML_ITEM)) {
menuState.readItem(attrs);
} else if (tagName.equals(XML_MENU)) {
// A menu start tag denotes a submenu for an item
SubMenu subMenu = menuState.addSubMenuItem();
// Parse the submenu into returned SubMenu
parseMenu(parser, attrs, subMenu);
} else {
lookingForEndOfUnknownTag = true;
unknownTagName = tagName;
}
break;
case XmlPullParser.END_TAG:
tagName = parser.getName();
if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {
lookingForEndOfUnknownTag = false;
unknownTagName = null;
} else if (tagName.equals(XML_GROUP)) {
menuState.resetGroup();
} else if (tagName.equals(XML_ITEM)) {
// Add the item if it hasn't been added (if the item was
// a submenu, it would have been added already)
if (!menuState.hasAddedItem()) {
if (menuState.itemActionProvider != null &&
menuState.itemActionProvider.hasSubMenu()) {
menuState.addSubMenuItem();
} else {
menuState.addItem();
}
}
} else if (tagName.equals(XML_MENU)) {
reachedEndOfMenu = true;
}
break;
case XmlPullParser.END_DOCUMENT:
throw new RuntimeException("Unexpected end of document");
}
eventType = parser.next();
}
}
private Object getRealOwner() {
if (mRealOwner == null) {
mRealOwner = findRealOwner(mContext);
}
return mRealOwner;
}
private Object findRealOwner(Object owner) {
if (owner instanceof Activity) {
return owner;
}
if (owner instanceof ContextWrapper) {
return findRealOwner(((ContextWrapper) owner).getBaseContext());
}
return owner;
}
private static class InflatedOnMenuItemClickListener
implements MenuItem.OnMenuItemClickListener {
private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
private Object mRealOwner;
private Method mMethod;
public InflatedOnMenuItemClickListener(Object realOwner, String methodName) {
mRealOwner = realOwner;
Class<?> c = realOwner.getClass();
try {
mMethod = c.getMethod(methodName, PARAM_TYPES);
} catch (Exception e) {
InflateException ex = new InflateException(
"Couldn't resolve menu item onClick handler " + methodName +
" in class " + c.getName());
ex.initCause(e);
throw ex;
}
}
public boolean onMenuItemClick(MenuItem item) {
try {
if (mMethod.getReturnType() == Boolean.TYPE) {
return (Boolean) mMethod.invoke(mRealOwner, item);
} else {
mMethod.invoke(mRealOwner, item);
return true;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* State for the current menu.
* <p>
* Groups can not be nested unless there is another menu (which will have
* its state class).
*/
private class MenuState {
private Menu menu;
/*
* Group state is set on items as they are added, allowing an item to
* override its group state. (As opposed to set on items at the group end tag.)
*/
private int groupId;
private int groupCategory;
private int groupOrder;
private int groupCheckable;
private boolean groupVisible;
private boolean groupEnabled;
private boolean itemAdded;
private int itemId;
private int itemCategoryOrder;
private CharSequence itemTitle;
private CharSequence itemTitleCondensed;
private int itemIconResId;
private char itemAlphabeticShortcut;
private char itemNumericShortcut;
/**
* Sync to attrs.xml enum:
* - 0: none
* - 1: all
* - 2: exclusive
*/
private int itemCheckable;
private boolean itemChecked;
private boolean itemVisible;
private boolean itemEnabled;
/**
* Sync to attrs.xml enum, values in MenuItem:
* - 0: never
* - 1: ifRoom
* - 2: always
* - -1: Safe sentinel for "no value".
*/
private int itemShowAsAction;
private int itemActionViewLayout;
private String itemActionViewClassName;
private String itemActionProviderClassName;
private String itemListenerMethodName;
private ActionProvider itemActionProvider;
private static final int defaultGroupId = NO_ID;
private static final int defaultItemId = NO_ID;
private static final int defaultItemCategory = 0;
private static final int defaultItemOrder = 0;
private static final int defaultItemCheckable = 0;
private static final boolean defaultItemChecked = false;
private static final boolean defaultItemVisible = true;
private static final boolean defaultItemEnabled = true;
public MenuState(final Menu menu) {
this.menu = menu;
resetGroup();
}
public void resetGroup() {
groupId = defaultGroupId;
groupCategory = defaultItemCategory;
groupOrder = defaultItemOrder;
groupCheckable = defaultItemCheckable;
groupVisible = defaultItemVisible;
groupEnabled = defaultItemEnabled;
}
/**
* Called when the parser is pointing to a group tag.
*/
public void readGroup(AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.MenuGroup);
groupId = a.getResourceId(R.styleable.MenuGroup_android_id, defaultGroupId);
groupCategory = a.getInt(
R.styleable.MenuGroup_android_menuCategory, defaultItemCategory);
groupOrder = a.getInt(R.styleable.MenuGroup_android_orderInCategory, defaultItemOrder);
groupCheckable = a.getInt(
R.styleable.MenuGroup_android_checkableBehavior, defaultItemCheckable);
groupVisible = a.getBoolean(R.styleable.MenuGroup_android_visible, defaultItemVisible);
groupEnabled = a.getBoolean(R.styleable.MenuGroup_android_enabled, defaultItemEnabled);
a.recycle();
}
/**
* Called when the parser is pointing to an item tag.
*/
public void readItem(AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.MenuItem);
// Inherit attributes from the group as default value
itemId = a.getResourceId(R.styleable.MenuItem_android_id, defaultItemId);
final int category = a.getInt(R.styleable.MenuItem_android_menuCategory, groupCategory);
final int order = a.getInt(R.styleable.MenuItem_android_orderInCategory, groupOrder);
itemCategoryOrder = (category & SupportMenu.CATEGORY_MASK) |
(order & SupportMenu.USER_MASK);
itemTitle = a.getText(R.styleable.MenuItem_android_title);
itemTitleCondensed = a.getText(R.styleable.MenuItem_android_titleCondensed);
itemIconResId = a.getResourceId(R.styleable.MenuItem_android_icon, 0);
itemAlphabeticShortcut =
getShortcut(a.getString(R.styleable.MenuItem_android_alphabeticShortcut));
itemNumericShortcut =
getShortcut(a.getString(R.styleable.MenuItem_android_numericShortcut));
if (a.hasValue(R.styleable.MenuItem_android_checkable)) {
// Item has attribute checkable, use it
itemCheckable = a.getBoolean(R.styleable.MenuItem_android_checkable, false) ? 1 : 0;
} else {
// Item does not have attribute, use the group's (group can have one more state
// for checkable that represents the exclusive checkable)
itemCheckable = groupCheckable;
}
itemChecked = a.getBoolean(R.styleable.MenuItem_android_checked, defaultItemChecked);
itemVisible = a.getBoolean(R.styleable.MenuItem_android_visible, groupVisible);
itemEnabled = a.getBoolean(R.styleable.MenuItem_android_enabled, groupEnabled);
itemShowAsAction = a.getInt(R.styleable.MenuItem_showAsAction, -1);
itemListenerMethodName = a.getString(R.styleable.MenuItem_android_onClick);
itemActionViewLayout = a.getResourceId(R.styleable.MenuItem_actionLayout, 0);
itemActionViewClassName = a.getString(R.styleable.MenuItem_actionViewClass);
itemActionProviderClassName = a.getString(R.styleable.MenuItem_actionProviderClass);
final boolean hasActionProvider = itemActionProviderClassName != null;
if (hasActionProvider && itemActionViewLayout == 0 && itemActionViewClassName == null) {
itemActionProvider = newInstance(itemActionProviderClassName,
ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE,
mActionProviderConstructorArguments);
} else {
if (hasActionProvider) {
Log.w(LOG_TAG, "Ignoring attribute 'actionProviderClass'."
+ " Action view already specified.");
}
itemActionProvider = null;
}
a.recycle();
itemAdded = false;
}
private char getShortcut(String shortcutString) {
if (shortcutString == null) {
return 0;
} else {
return shortcutString.charAt(0);
}
}
private void setItem(MenuItem item) {
item.setChecked(itemChecked)
.setVisible(itemVisible)
.setEnabled(itemEnabled)
.setCheckable(itemCheckable >= 1)
.setTitleCondensed(itemTitleCondensed)
.setIcon(itemIconResId)
.setAlphabeticShortcut(itemAlphabeticShortcut)
.setNumericShortcut(itemNumericShortcut);
if (itemShowAsAction >= 0) {
MenuItemCompat.setShowAsAction(item, itemShowAsAction);
}
if (itemListenerMethodName != null) {
if (mContext.isRestricted()) {
throw new IllegalStateException("The android:onClick attribute cannot "
+ "be used within a restricted context");
}
item.setOnMenuItemClickListener(
new InflatedOnMenuItemClickListener(getRealOwner(), itemListenerMethodName));
}
final MenuItemImpl impl = item instanceof MenuItemImpl ? (MenuItemImpl) item : null;
if (itemCheckable >= 2) {
if (item instanceof MenuItemImpl) {
((MenuItemImpl) item).setExclusiveCheckable(true);
} else if (item instanceof MenuItemWrapperICS) {
((MenuItemWrapperICS) item).setExclusiveCheckable(true);
}
}
boolean actionViewSpecified = false;
if (itemActionViewClassName != null) {
View actionView = (View) newInstance(itemActionViewClassName,
ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments);
MenuItemCompat.setActionView(item, actionView);
actionViewSpecified = true;
}
if (itemActionViewLayout > 0) {
if (!actionViewSpecified) {
MenuItemCompat.setActionView(item, itemActionViewLayout);
actionViewSpecified = true;
} else {
Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'."
+ " Action view already specified.");
}
}
if (itemActionProvider != null) {
MenuItemCompat.setActionProvider(item, itemActionProvider);
}
}
public void addItem() {
itemAdded = true;
setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
}
public SubMenu addSubMenuItem() {
itemAdded = true;
SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
setItem(subMenu.getItem());
return subMenu;
}
public boolean hasAddedItem() {
return itemAdded;
}
@SuppressWarnings("unchecked")
private <T> T newInstance(String className, Class<?>[] constructorSignature,
Object[] arguments) {
try {
Class<?> clazz = mContext.getClassLoader().loadClass(className);
Constructor<?> constructor = clazz.getConstructor(constructorSignature);
return (T) constructor.newInstance(arguments);
} catch (Exception e) {
Log.w(LOG_TAG, "Cannot instantiate class: " + className, e);
}
return null;
}
}
}

View file

@ -0,0 +1,139 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.view;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.view.View;
import android.view.animation.Interpolator;
import java.util.ArrayList;
/**
* A very naive implementation of a set of
* {@link android.support.v4.view.ViewPropertyAnimatorCompat}.
*
* @hide
*/
public class ViewPropertyAnimatorCompatSet {
private final ArrayList<ViewPropertyAnimatorCompat> mAnimators;
private long mDuration = -1;
private Interpolator mInterpolator;
private ViewPropertyAnimatorListener mListener;
private boolean mIsStarted;
public ViewPropertyAnimatorCompatSet() {
mAnimators = new ArrayList<ViewPropertyAnimatorCompat>();
}
public ViewPropertyAnimatorCompatSet play(ViewPropertyAnimatorCompat animator) {
if (!mIsStarted) {
mAnimators.add(animator);
}
return this;
}
public void start() {
if (mIsStarted) return;
for (ViewPropertyAnimatorCompat animator : mAnimators) {
if (mDuration >= 0) {
animator.setDuration(mDuration);
}
if (mInterpolator != null) {
animator.setInterpolator(mInterpolator);
}
if (mListener != null) {
animator.setListener(mProxyListener);
}
animator.start();
}
mIsStarted = true;
}
private void onAnimationsEnded() {
mIsStarted = false;
}
public void cancel() {
if (!mIsStarted) {
return;
}
for (ViewPropertyAnimatorCompat animator : mAnimators) {
animator.cancel();
}
mIsStarted = false;
}
public ViewPropertyAnimatorCompatSet setDuration(long duration) {
if (!mIsStarted) {
mDuration = duration;
}
return this;
}
public ViewPropertyAnimatorCompatSet setInterpolator(Interpolator interpolator) {
if (!mIsStarted) {
mInterpolator = interpolator;
}
return this;
}
public ViewPropertyAnimatorCompatSet setListener(ViewPropertyAnimatorListener listener) {
if (!mIsStarted) {
mListener = listener;
}
return this;
}
private final ViewPropertyAnimatorListenerAdapter mProxyListener
= new ViewPropertyAnimatorListenerAdapter() {
private boolean mProxyStarted = false;
private int mProxyEndCount = 0;
@Override
public void onAnimationStart(View view) {
if (mProxyStarted) {
return;
}
mProxyStarted = true;
if (mListener != null) {
mListener.onAnimationStart(null);
}
}
void onEnd() {
mProxyEndCount = 0;
mProxyStarted = false;
onAnimationsEnded();
}
@Override
public void onAnimationEnd(View view) {
if (++mProxyEndCount == mAnimators.size()) {
if (mListener != null) {
mListener.onAnimationEnd(null);
}
onEnd();
}
}
};
}

View file

@ -0,0 +1,151 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.view;
import android.view.ActionMode;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
/**
* A simple decorator stub for Window.Callback that passes through any calls
* to the wrapped instance as a base implementation. Call super.foo() to call into
* the wrapped callback for any subclasses.
*
* @hide
*/
public class WindowCallbackWrapper implements Window.Callback {
final Window.Callback mWrapped;
public WindowCallbackWrapper(Window.Callback wrapped) {
if (wrapped == null) {
throw new IllegalArgumentException("Window callback may not be null");
}
mWrapped = wrapped;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return mWrapped.dispatchKeyEvent(event);
}
@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
return mWrapped.dispatchKeyShortcutEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return mWrapped.dispatchTouchEvent(event);
}
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
return mWrapped.dispatchTrackballEvent(event);
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
return mWrapped.dispatchGenericMotionEvent(event);
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
return mWrapped.dispatchPopulateAccessibilityEvent(event);
}
@Override
public View onCreatePanelView(int featureId) {
return mWrapped.onCreatePanelView(featureId);
}
@Override
public boolean onCreatePanelMenu(int featureId, Menu menu) {
return mWrapped.onCreatePanelMenu(featureId, menu);
}
@Override
public boolean onPreparePanel(int featureId, View view, Menu menu) {
return mWrapped.onPreparePanel(featureId, view, menu);
}
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
return mWrapped.onMenuOpened(featureId, menu);
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
return mWrapped.onMenuItemSelected(featureId, item);
}
@Override
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
mWrapped.onWindowAttributesChanged(attrs);
}
@Override
public void onContentChanged() {
mWrapped.onContentChanged();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
mWrapped.onWindowFocusChanged(hasFocus);
}
@Override
public void onAttachedToWindow() {
mWrapped.onAttachedToWindow();
}
@Override
public void onDetachedFromWindow() {
mWrapped.onDetachedFromWindow();
}
@Override
public void onPanelClosed(int featureId, Menu menu) {
mWrapped.onPanelClosed(featureId, menu);
}
@Override
public boolean onSearchRequested() {
return mWrapped.onSearchRequested();
}
@Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
return mWrapped.onWindowStartingActionMode(callback);
}
@Override
public void onActionModeStarted(ActionMode mode) {
mWrapped.onActionModeStarted(mode);
}
@Override
public void onActionModeFinished(ActionMode mode) {
mWrapped.onActionModeFinished(mode);
}
}

View file

@ -0,0 +1,296 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ActionProvider;
import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.view.MenuItemCompat;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
/**
* @hide
*/
public class ActionMenuItem implements SupportMenuItem {
private final int mId;
private final int mGroup;
private final int mCategoryOrder;
private final int mOrdering;
private CharSequence mTitle;
private CharSequence mTitleCondensed;
private Intent mIntent;
private char mShortcutNumericChar;
private char mShortcutAlphabeticChar;
private Drawable mIconDrawable;
private int mIconResId = NO_ICON;
private Context mContext;
private SupportMenuItem.OnMenuItemClickListener mClickListener;
private static final int NO_ICON = 0;
private int mFlags = ENABLED;
private static final int CHECKABLE = 0x00000001;
private static final int CHECKED = 0x00000002;
private static final int EXCLUSIVE = 0x00000004;
private static final int HIDDEN = 0x00000008;
private static final int ENABLED = 0x00000010;
public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering,
CharSequence title) {
mContext = context;
mId = id;
mGroup = group;
mCategoryOrder = categoryOrder;
mOrdering = ordering;
mTitle = title;
}
public char getAlphabeticShortcut() {
return mShortcutAlphabeticChar;
}
public int getGroupId() {
return mGroup;
}
public Drawable getIcon() {
return mIconDrawable;
}
public Intent getIntent() {
return mIntent;
}
public int getItemId() {
return mId;
}
public ContextMenuInfo getMenuInfo() {
return null;
}
public char getNumericShortcut() {
return mShortcutNumericChar;
}
public int getOrder() {
return mOrdering;
}
public SubMenu getSubMenu() {
return null;
}
public CharSequence getTitle() {
return mTitle;
}
public CharSequence getTitleCondensed() {
return mTitleCondensed != null ? mTitleCondensed : mTitle;
}
public boolean hasSubMenu() {
return false;
}
public boolean isCheckable() {
return (mFlags & CHECKABLE) != 0;
}
public boolean isChecked() {
return (mFlags & CHECKED) != 0;
}
public boolean isEnabled() {
return (mFlags & ENABLED) != 0;
}
public boolean isVisible() {
return (mFlags & HIDDEN) == 0;
}
public MenuItem setAlphabeticShortcut(char alphaChar) {
mShortcutAlphabeticChar = alphaChar;
return this;
}
public MenuItem setCheckable(boolean checkable) {
mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
return this;
}
public ActionMenuItem setExclusiveCheckable(boolean exclusive) {
mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
return this;
}
public MenuItem setChecked(boolean checked) {
mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
return this;
}
public MenuItem setEnabled(boolean enabled) {
mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0);
return this;
}
public MenuItem setIcon(Drawable icon) {
mIconDrawable = icon;
mIconResId = NO_ICON;
return this;
}
public MenuItem setIcon(int iconRes) {
mIconResId = iconRes;
mIconDrawable = ContextCompat.getDrawable(mContext, iconRes);
return this;
}
public MenuItem setIntent(Intent intent) {
mIntent = intent;
return this;
}
public MenuItem setNumericShortcut(char numericChar) {
mShortcutNumericChar = numericChar;
return this;
}
public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
mClickListener = menuItemClickListener;
return this;
}
public MenuItem setShortcut(char numericChar, char alphaChar) {
mShortcutNumericChar = numericChar;
mShortcutAlphabeticChar = alphaChar;
return this;
}
public MenuItem setTitle(CharSequence title) {
mTitle = title;
return this;
}
public MenuItem setTitle(int title) {
mTitle = mContext.getResources().getString(title);
return this;
}
public MenuItem setTitleCondensed(CharSequence title) {
mTitleCondensed = title;
return this;
}
public MenuItem setVisible(boolean visible) {
mFlags = (mFlags & HIDDEN) | (visible ? 0 : HIDDEN);
return this;
}
public boolean invoke() {
if (mClickListener != null && mClickListener.onMenuItemClick(this)) {
return true;
}
if (mIntent != null) {
mContext.startActivity(mIntent);
return true;
}
return false;
}
public void setShowAsAction(int show) {
// Do nothing. ActionMenuItems always show as action buttons.
}
public SupportMenuItem setActionView(View actionView) {
throw new UnsupportedOperationException();
}
public View getActionView() {
return null;
}
@Override
public MenuItem setActionProvider(android.view.ActionProvider actionProvider) {
throw new UnsupportedOperationException();
}
@Override
public android.view.ActionProvider getActionProvider() {
throw new UnsupportedOperationException();
}
@Override
public SupportMenuItem setActionView(int resId) {
throw new UnsupportedOperationException();
}
@Override
public ActionProvider getSupportActionProvider() {
return null;
}
@Override
public SupportMenuItem setSupportActionProvider(ActionProvider actionProvider) {
throw new UnsupportedOperationException();
}
@Override
public SupportMenuItem setShowAsActionFlags(int actionEnum) {
setShowAsAction(actionEnum);
return this;
}
@Override
public boolean expandActionView() {
return false;
}
@Override
public boolean collapseActionView() {
return false;
}
@Override
public boolean isActionViewExpanded() {
return false;
}
@Override
public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) {
throw new UnsupportedOperationException();
}
@Override
public SupportMenuItem setSupportOnActionExpandListener(MenuItemCompat.OnActionExpandListener listener) {
// No need to save the listener; ActionMenuItem does not support collapsing items.
return this;
}
}

View file

@ -0,0 +1,334 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.support.v7.internal.text.AllCapsTransformationMethod;
import android.support.v7.internal.widget.CompatTextView;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.ListPopupWindow;
import android.text.TextUtils;
import android.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import java.util.Locale;
/**
* @hide
*/
public class ActionMenuItemView extends CompatTextView
implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
ActionMenuView.ActionMenuChildView {
private static final String TAG = "ActionMenuItemView";
private MenuItemImpl mItemData;
private CharSequence mTitle;
private Drawable mIcon;
private MenuBuilder.ItemInvoker mItemInvoker;
private ListPopupWindow.ForwardingListener mForwardingListener;
private PopupCallback mPopupCallback;
private boolean mAllowTextWithIcon;
private boolean mExpandedFormat;
private int mMinWidth;
private int mSavedPaddingLeft;
private static final int MAX_ICON_SIZE = 32; // dp
private int mMaxIconSize;
public ActionMenuItemView(Context context) {
this(context, null);
}
public ActionMenuItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final Resources res = context.getResources();
mAllowTextWithIcon = res.getBoolean(
R.bool.abc_config_allowActionMenuItemTextWithIcon);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ActionMenuItemView, defStyle, 0);
mMinWidth = a.getDimensionPixelSize(
R.styleable.ActionMenuItemView_android_minWidth, 0);
a.recycle();
final float density = res.getDisplayMetrics().density;
mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
setOnClickListener(this);
setOnLongClickListener(this);
mSavedPaddingLeft = -1;
}
public void onConfigurationChanged(Configuration newConfig) {
if (Build.VERSION.SDK_INT >= 8) {
super.onConfigurationChanged(newConfig);
}
mAllowTextWithIcon = getContext().getResources().getBoolean(
R.bool.abc_config_allowActionMenuItemTextWithIcon);
updateTextButtonVisibility();
}
@Override
public void setPadding(int l, int t, int r, int b) {
mSavedPaddingLeft = l;
super.setPadding(l, t, r, b);
}
public MenuItemImpl getItemData() {
return mItemData;
}
public void initialize(MenuItemImpl itemData, int menuType) {
mItemData = itemData;
setIcon(itemData.getIcon());
setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon
setId(itemData.getItemId());
setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
setEnabled(itemData.isEnabled());
if (itemData.hasSubMenu()) {
if (mForwardingListener == null) {
mForwardingListener = new ActionMenuItemForwardingListener();
}
}
}
@Override
public boolean onTouchEvent(MotionEvent e) {
if (mItemData.hasSubMenu() && mForwardingListener != null
&& mForwardingListener.onTouch(this, e)) {
return true;
}
return super.onTouchEvent(e);
}
@Override
public void onClick(View v) {
if (mItemInvoker != null) {
mItemInvoker.invokeItem(mItemData);
}
}
public void setItemInvoker(MenuBuilder.ItemInvoker invoker) {
mItemInvoker = invoker;
}
public void setPopupCallback(PopupCallback popupCallback) {
mPopupCallback = popupCallback;
}
public boolean prefersCondensedTitle() {
return true;
}
public void setCheckable(boolean checkable) {
// TODO Support checkable action items
}
public void setChecked(boolean checked) {
// TODO Support checkable action items
}
public void setExpandedFormat(boolean expandedFormat) {
if (mExpandedFormat != expandedFormat) {
mExpandedFormat = expandedFormat;
if (mItemData != null) {
mItemData.actionFormatChanged();
}
}
}
private void updateTextButtonVisibility() {
boolean visible = !TextUtils.isEmpty(mTitle);
visible &= mIcon == null ||
(mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
setText(visible ? mTitle : null);
}
public void setIcon(Drawable icon) {
mIcon = icon;
if (icon != null) {
int width = icon.getIntrinsicWidth();
int height = icon.getIntrinsicHeight();
if (width > mMaxIconSize) {
final float scale = (float) mMaxIconSize / width;
width = mMaxIconSize;
height *= scale;
}
if (height > mMaxIconSize) {
final float scale = (float) mMaxIconSize / height;
height = mMaxIconSize;
width *= scale;
}
icon.setBounds(0, 0, width, height);
}
setCompoundDrawables(icon, null, null, null);
updateTextButtonVisibility();
}
public boolean hasText() {
return !TextUtils.isEmpty(getText());
}
public void setShortcut(boolean showShortcut, char shortcutKey) {
// Action buttons don't show text for shortcut keys.
}
public void setTitle(CharSequence title) {
mTitle = title;
setContentDescription(mTitle);
updateTextButtonVisibility();
}
public boolean showsIcon() {
return true;
}
public boolean needsDividerBefore() {
return hasText() && mItemData.getIcon() == null;
}
public boolean needsDividerAfter() {
return hasText();
}
@Override
public boolean onLongClick(View v) {
if (hasText()) {
// Don't show the cheat sheet for items that already show text.
return false;
}
final int[] screenPos = new int[2];
final Rect displayFrame = new Rect();
getLocationOnScreen(screenPos);
getWindowVisibleDisplayFrame(displayFrame);
final Context context = getContext();
final int width = getWidth();
final int height = getHeight();
final int midy = screenPos[1] + height / 2;
int referenceX = screenPos[0] + width / 2;
if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
referenceX = screenWidth - referenceX; // mirror
}
Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
if (midy < displayFrame.height()) {
// Show along the top; follow action buttons
cheatSheet.setGravity(Gravity.TOP | GravityCompat.END, referenceX, height);
} else {
// Show along the bottom center
cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
}
cheatSheet.show();
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final boolean textVisible = hasText();
if (textVisible && mSavedPaddingLeft >= 0) {
super.setPadding(mSavedPaddingLeft, getPaddingTop(),
getPaddingRight(), getPaddingBottom());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int oldMeasuredWidth = getMeasuredWidth();
final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(widthSize, mMinWidth)
: mMinWidth;
if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) {
// Remeasure at exactly the minimum width.
super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
heightMeasureSpec);
}
if (!textVisible && mIcon != null) {
// TextView won't center compound drawables in both dimensions without
// a little coercion. Pad in to center the icon after we've measured.
final int w = getMeasuredWidth();
final int dw = mIcon.getBounds().width();
super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom());
}
}
private class ActionMenuItemForwardingListener extends ListPopupWindow.ForwardingListener {
public ActionMenuItemForwardingListener() {
super(ActionMenuItemView.this);
}
@Override
public ListPopupWindow getPopup() {
if (mPopupCallback != null) {
return mPopupCallback.getPopup();
}
return null;
}
@Override
protected boolean onForwardingStarted() {
// Call the invoker, then check if the expected popup is showing.
if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
final ListPopupWindow popup = getPopup();
return popup != null && popup.isShowing();
}
return false;
}
@Override
protected boolean onForwardingStopped() {
final ListPopupWindow popup = getPopup();
if (popup != null) {
popup.dismiss();
return true;
}
return false;
}
}
public static abstract class PopupCallback {
public abstract ListPopupWindow getPopup();
}
}

View file

@ -0,0 +1,237 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
/**
* Base class for MenuPresenters that have a consistent container view and item views. Behaves
* similarly to an AdapterView in that existing item views will be reused if possible when items
* change.
*
* @hide
*/
public abstract class BaseMenuPresenter implements MenuPresenter {
protected Context mSystemContext;
protected Context mContext;
protected MenuBuilder mMenu;
protected LayoutInflater mSystemInflater;
protected LayoutInflater mInflater;
private Callback mCallback;
private int mMenuLayoutRes;
private int mItemLayoutRes;
protected MenuView mMenuView;
private int mId;
/**
* Construct a new BaseMenuPresenter.
*
* @param context Context for generating system-supplied views
* @param menuLayoutRes Layout resource ID for the menu container view
* @param itemLayoutRes Layout resource ID for a single item view
*/
public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) {
mSystemContext = context;
mSystemInflater = LayoutInflater.from(context);
mMenuLayoutRes = menuLayoutRes;
mItemLayoutRes = itemLayoutRes;
}
@Override
public void initForMenu(Context context, MenuBuilder menu) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
mMenu = menu;
}
@Override
public MenuView getMenuView(ViewGroup root) {
if (mMenuView == null) {
mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false);
mMenuView.initialize(mMenu);
updateMenuView(true);
}
return mMenuView;
}
/**
* Reuses item views when it can
*/
public void updateMenuView(boolean cleared) {
final ViewGroup parent = (ViewGroup) mMenuView;
if (parent == null) return;
int childIndex = 0;
if (mMenu != null) {
mMenu.flagActionItems();
ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
final int itemCount = visibleItems.size();
for (int i = 0; i < itemCount; i++) {
MenuItemImpl item = visibleItems.get(i);
if (shouldIncludeItem(childIndex, item)) {
final View convertView = parent.getChildAt(childIndex);
final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ?
((MenuView.ItemView) convertView).getItemData() : null;
final View itemView = getItemView(item, convertView, parent);
if (item != oldItem) {
// Don't let old states linger with new data.
itemView.setPressed(false);
ViewCompat.jumpDrawablesToCurrentState(itemView);
}
if (itemView != convertView) {
addItemView(itemView, childIndex);
}
childIndex++;
}
}
}
// Remove leftover views.
while (childIndex < parent.getChildCount()) {
if (!filterLeftoverView(parent, childIndex)) {
childIndex++;
}
}
}
/**
* Add an item view at the given index.
*
* @param itemView View to add
* @param childIndex Index within the parent to insert at
*/
protected void addItemView(View itemView, int childIndex) {
final ViewGroup currentParent = (ViewGroup) itemView.getParent();
if (currentParent != null) {
currentParent.removeView(itemView);
}
((ViewGroup) mMenuView).addView(itemView, childIndex);
}
/**
* Filter the child view at index and remove it if appropriate.
* @param parent Parent to filter from
* @param childIndex Index to filter
* @return true if the child view at index was removed
*/
protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
parent.removeViewAt(childIndex);
return true;
}
public void setCallback(Callback cb) {
mCallback = cb;
}
public Callback getCallback() {
return mCallback;
}
/**
* Create a new item view that can be re-bound to other item data later.
*
* @return The new item view
*/
public MenuView.ItemView createItemView(ViewGroup parent) {
return (MenuView.ItemView) mSystemInflater.inflate(mItemLayoutRes, parent, false);
}
/**
* Prepare an item view for use. See AdapterView for the basic idea at work here.
* This may require creating a new item view, but well-behaved implementations will
* re-use the view passed as convertView if present. The returned view will be populated
* with data from the item parameter.
*
* @param item Item to present
* @param convertView Existing view to reuse
* @param parent Intended parent view - use for inflation.
* @return View that presents the requested menu item
*/
public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
MenuView.ItemView itemView;
if (convertView instanceof MenuView.ItemView) {
itemView = (MenuView.ItemView) convertView;
} else {
itemView = createItemView(parent);
}
bindItemView(item, itemView);
return (View) itemView;
}
/**
* Bind item data to an existing item view.
*
* @param item Item to bind
* @param itemView View to populate with item data
*/
public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView);
/**
* Filter item by child index and item data.
*
* @param childIndex Indended presentation index of this item
* @param item Item to present
* @return true if this item should be included in this menu presentation; false otherwise
*/
public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
return true;
}
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
if (mCallback != null) {
mCallback.onCloseMenu(menu, allMenusAreClosing);
}
}
public boolean onSubMenuSelected(SubMenuBuilder menu) {
if (mCallback != null) {
return mCallback.onOpenSubMenu(menu);
}
return false;
}
public boolean flagActionItems() {
return false;
}
public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
return false;
}
public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
return false;
}
public int getId() {
return mId;
}
public void setId(int id) {
mId = id;
}
}

View file

@ -0,0 +1,126 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.internal.view.SupportSubMenu;
import android.support.v4.util.ArrayMap;
import android.view.MenuItem;
import android.view.SubMenu;
import java.util.Iterator;
import java.util.Map;
abstract class BaseMenuWrapper<T> extends BaseWrapper<T> {
final Context mContext;
private Map<SupportMenuItem, MenuItem> mMenuItems;
private Map<SupportSubMenu, SubMenu> mSubMenus;
BaseMenuWrapper(Context context, T object) {
super(object);
mContext = context;
}
final MenuItem getMenuItemWrapper(final MenuItem menuItem) {
if (menuItem instanceof SupportMenuItem) {
final SupportMenuItem supportMenuItem = (SupportMenuItem) menuItem;
// Instantiate Map if null
if (mMenuItems == null) {
mMenuItems = new ArrayMap<>();
}
// First check if we already have a wrapper for this item
MenuItem wrappedItem = mMenuItems.get(menuItem);
if (null == wrappedItem) {
// ... if not, create one and add it to our map
wrappedItem = MenuWrapperFactory.wrapSupportMenuItem(mContext, supportMenuItem);
mMenuItems.put(supportMenuItem, wrappedItem);
}
return wrappedItem;
}
return menuItem;
}
final SubMenu getSubMenuWrapper(final SubMenu subMenu) {
if (subMenu instanceof SupportSubMenu) {
final SupportSubMenu supportSubMenu = (SupportSubMenu) subMenu;
// Instantiate Map if null
if (mSubMenus == null) {
mSubMenus = new ArrayMap<>();
}
SubMenu wrappedMenu = mSubMenus.get(supportSubMenu);
if (null == wrappedMenu) {
wrappedMenu = MenuWrapperFactory.wrapSupportSubMenu(mContext, supportSubMenu);
mSubMenus.put(supportSubMenu, wrappedMenu);
}
return wrappedMenu;
}
return subMenu;
}
final void internalClear() {
if (mMenuItems != null) {
mMenuItems.clear();
}
if (mSubMenus != null) {
mSubMenus.clear();
}
}
final void internalRemoveGroup(final int groupId) {
if (mMenuItems == null) {
return;
}
Iterator<SupportMenuItem> iterator = mMenuItems.keySet().iterator();
android.view.MenuItem menuItem;
while (iterator.hasNext()) {
menuItem = iterator.next();
if (groupId == menuItem.getGroupId()) {
iterator.remove();
}
}
}
final void internalRemoveItem(final int id) {
if (mMenuItems == null) {
return;
}
Iterator<SupportMenuItem> iterator = mMenuItems.keySet().iterator();
android.view.MenuItem menuItem;
while (iterator.hasNext()) {
menuItem = iterator.next();
if (id == menuItem.getItemId()) {
iterator.remove();
break;
}
}
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
class BaseWrapper<T> {
final T mWrappedObject;
BaseWrapper(T object) {
if (null == object) {
throw new IllegalArgumentException("Wrapped Object can not be null.");
}
mWrappedObject = object;
}
public T getWrappedObject() {
return mWrappedObject;
}
}

View file

@ -0,0 +1,97 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.view.menu.MenuBuilder.ItemInvoker;
import android.support.v7.internal.view.menu.MenuView;
import android.support.v7.internal.widget.TintTypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
/**
* The expanded menu view is a list-like menu with all of the available menu items. It is opened
* by the user clicking no the 'More' button on the icon menu view.
*
* @hide
*/
public final class ExpandedMenuView extends ListView
implements ItemInvoker, MenuView, OnItemClickListener {
private static final int[] TINT_ATTRS = {
android.R.attr.background,
android.R.attr.divider
};
private MenuBuilder mMenu;
/** Default animations for this menu */
private int mAnimations;
public ExpandedMenuView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.listViewStyle);
}
public ExpandedMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs);
setOnItemClickListener(this);
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
defStyleAttr, 0);
if (a.hasValue(0)) {
setBackgroundDrawable(a.getDrawable(0));
}
if (a.hasValue(1)) {
setDivider(a.getDrawable(1));
}
a.recycle();
}
@Override
public void initialize(MenuBuilder menu) {
mMenu = menu;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
// Clear the cached bitmaps of children
setChildrenDrawingCacheEnabled(false);
}
@Override
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
@Override
@SuppressWarnings("rawtypes")
public void onItemClick(AdapterView parent, View v, int position, long id) {
invokeItem((MenuItemImpl) getAdapter().getItem(position));
}
@Override
public int getWindowAnimations() {
return mAnimations;
}
}

View file

@ -0,0 +1,282 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.TextView;
/**
* The item view for each item in the ListView-based MenuViews.
*
* @hide
*/
public class ListMenuItemView extends LinearLayout implements MenuView.ItemView {
private static final String TAG = "ListMenuItemView";
private MenuItemImpl mItemData;
private ImageView mIconView;
private RadioButton mRadioButton;
private TextView mTitleView;
private CheckBox mCheckBox;
private TextView mShortcutView;
private Drawable mBackground;
private int mTextAppearance;
private Context mTextAppearanceContext;
private boolean mPreserveIconSpacing;
private int mMenuType;
private Context mContext;
private LayoutInflater mInflater;
private boolean mForceShowIcon;
public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
mContext = context;
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.MenuView, defStyle, 0);
mBackground = a.getDrawable(R.styleable.MenuView_android_itemBackground);
mTextAppearance = a.getResourceId(R.styleable.
MenuView_android_itemTextAppearance, -1);
mPreserveIconSpacing = a.getBoolean(
R.styleable.MenuView_preserveIconSpacing, false);
mTextAppearanceContext = context;
a.recycle();
}
public ListMenuItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setBackgroundDrawable(mBackground);
mTitleView = (TextView) findViewById(R.id.title);
if (mTextAppearance != -1) {
mTitleView.setTextAppearance(mTextAppearanceContext,
mTextAppearance);
}
mShortcutView = (TextView) findViewById(R.id.shortcut);
}
public void initialize(MenuItemImpl itemData, int menuType) {
mItemData = itemData;
mMenuType = menuType;
setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
setTitle(itemData.getTitleForItemView(this));
setCheckable(itemData.isCheckable());
setShortcut(itemData.shouldShowShortcut(), itemData.getShortcut());
setIcon(itemData.getIcon());
setEnabled(itemData.isEnabled());
}
public void setForceShowIcon(boolean forceShow) {
mPreserveIconSpacing = mForceShowIcon = forceShow;
}
public void setTitle(CharSequence title) {
if (title != null) {
mTitleView.setText(title);
if (mTitleView.getVisibility() != VISIBLE) mTitleView.setVisibility(VISIBLE);
} else {
if (mTitleView.getVisibility() != GONE) mTitleView.setVisibility(GONE);
}
}
public MenuItemImpl getItemData() {
return mItemData;
}
public void setCheckable(boolean checkable) {
if (!checkable && mRadioButton == null && mCheckBox == null) {
return;
}
// Depending on whether its exclusive check or not, the checkbox or
// radio button will be the one in use (and the other will be otherCompoundButton)
final CompoundButton compoundButton;
final CompoundButton otherCompoundButton;
if (mItemData.isExclusiveCheckable()) {
if (mRadioButton == null) {
insertRadioButton();
}
compoundButton = mRadioButton;
otherCompoundButton = mCheckBox;
} else {
if (mCheckBox == null) {
insertCheckBox();
}
compoundButton = mCheckBox;
otherCompoundButton = mRadioButton;
}
if (checkable) {
compoundButton.setChecked(mItemData.isChecked());
final int newVisibility = checkable ? VISIBLE : GONE;
if (compoundButton.getVisibility() != newVisibility) {
compoundButton.setVisibility(newVisibility);
}
// Make sure the other compound button isn't visible
if (otherCompoundButton != null && otherCompoundButton.getVisibility() != GONE) {
otherCompoundButton.setVisibility(GONE);
}
} else {
if (mCheckBox != null) {
mCheckBox.setVisibility(GONE);
}
if (mRadioButton != null) {
mRadioButton.setVisibility(GONE);
}
}
}
public void setChecked(boolean checked) {
CompoundButton compoundButton;
if (mItemData.isExclusiveCheckable()) {
if (mRadioButton == null) {
insertRadioButton();
}
compoundButton = mRadioButton;
} else {
if (mCheckBox == null) {
insertCheckBox();
}
compoundButton = mCheckBox;
}
compoundButton.setChecked(checked);
}
public void setShortcut(boolean showShortcut, char shortcutKey) {
final int newVisibility = (showShortcut && mItemData.shouldShowShortcut())
? VISIBLE : GONE;
if (newVisibility == VISIBLE) {
mShortcutView.setText(mItemData.getShortcutLabel());
}
if (mShortcutView.getVisibility() != newVisibility) {
mShortcutView.setVisibility(newVisibility);
}
}
public void setIcon(Drawable icon) {
final boolean showIcon = mItemData.shouldShowIcon() || mForceShowIcon;
if (!showIcon && !mPreserveIconSpacing) {
return;
}
if (mIconView == null && icon == null && !mPreserveIconSpacing) {
return;
}
if (mIconView == null) {
insertIconView();
}
if (icon != null || mPreserveIconSpacing) {
mIconView.setImageDrawable(showIcon ? icon : null);
if (mIconView.getVisibility() != VISIBLE) {
mIconView.setVisibility(VISIBLE);
}
} else {
mIconView.setVisibility(GONE);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mIconView != null && mPreserveIconSpacing) {
// Enforce minimum icon spacing
ViewGroup.LayoutParams lp = getLayoutParams();
LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
if (lp.height > 0 && iconLp.width <= 0) {
iconLp.width = lp.height;
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void insertIconView() {
LayoutInflater inflater = getInflater();
mIconView = (ImageView) inflater.inflate(R.layout.abc_list_menu_item_icon,
this, false);
addView(mIconView, 0);
}
private void insertRadioButton() {
LayoutInflater inflater = getInflater();
mRadioButton =
(RadioButton) inflater.inflate(R.layout.abc_list_menu_item_radio,
this, false);
addView(mRadioButton);
}
private void insertCheckBox() {
LayoutInflater inflater = getInflater();
mCheckBox =
(CheckBox) inflater.inflate(R.layout.abc_list_menu_item_checkbox,
this, false);
addView(mCheckBox);
}
public boolean prefersCondensedTitle() {
return false;
}
public boolean showsIcon() {
return mForceShowIcon;
}
private LayoutInflater getInflater() {
if (mInflater == null) {
mInflater = LayoutInflater.from(mContext);
}
return mInflater;
}
}

View file

@ -0,0 +1,288 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.database.DataSetObserver;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v7.appcompat.R;
import android.util.SparseArray;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import java.util.ArrayList;
/**
* MenuPresenter for list-style menus.
*
* @hide
*/
public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener {
private static final String TAG = "ListMenuPresenter";
Context mContext;
LayoutInflater mInflater;
MenuBuilder mMenu;
ExpandedMenuView mMenuView;
private int mItemIndexOffset;
int mThemeRes;
int mItemLayoutRes;
private Callback mCallback;
MenuAdapter mAdapter;
private int mId;
public static final String VIEWS_TAG = "android:menu:list";
/**
* Construct a new ListMenuPresenter.
* @param context Context to use for theming. This will supersede the context provided
* to initForMenu when this presenter is added.
* @param itemLayoutRes Layout resource for individual item views.
*/
public ListMenuPresenter(Context context, int itemLayoutRes) {
this(itemLayoutRes, 0);
mContext = context;
mInflater = LayoutInflater.from(mContext);
}
/**
* Construct a new ListMenuPresenter.
* @param itemLayoutRes Layout resource for individual item views.
* @param themeRes Resource ID of a theme to use for views.
*/
public ListMenuPresenter(int itemLayoutRes, int themeRes) {
mItemLayoutRes = itemLayoutRes;
mThemeRes = themeRes;
}
@Override
public void initForMenu(Context context, MenuBuilder menu) {
if (mThemeRes != 0) {
mContext = new ContextThemeWrapper(context, mThemeRes);
mInflater = LayoutInflater.from(mContext);
} else if (mContext != null) {
mContext = context;
if (mInflater == null) {
mInflater = LayoutInflater.from(mContext);
}
}
mMenu = menu;
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
@Override
public MenuView getMenuView(ViewGroup root) {
if (mMenuView == null) {
mMenuView = (ExpandedMenuView) mInflater.inflate(
R.layout.abc_expanded_menu_layout, root, false);
if (mAdapter == null) {
mAdapter = new MenuAdapter();
}
mMenuView.setAdapter(mAdapter);
mMenuView.setOnItemClickListener(this);
}
return mMenuView;
}
/**
* Call this instead of getMenuView if you want to manage your own ListView.
* For proper operation, the ListView hosting this adapter should add
* this presenter as an OnItemClickListener.
*
* @return A ListAdapter containing the items in the menu.
*/
public ListAdapter getAdapter() {
if (mAdapter == null) {
mAdapter = new MenuAdapter();
}
return mAdapter;
}
@Override
public void updateMenuView(boolean cleared) {
if (mAdapter != null) mAdapter.notifyDataSetChanged();
}
@Override
public void setCallback(Callback cb) {
mCallback = cb;
}
@Override
public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
if (!subMenu.hasVisibleItems()) return false;
// The window manager will give us a token.
new MenuDialogHelper(subMenu).show(null);
if (mCallback != null) {
mCallback.onOpenSubMenu(subMenu);
}
return true;
}
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
if (mCallback != null) {
mCallback.onCloseMenu(menu, allMenusAreClosing);
}
}
int getItemIndexOffset() {
return mItemIndexOffset;
}
public void setItemIndexOffset(int offset) {
mItemIndexOffset = offset;
if (mMenuView != null) {
updateMenuView(false);
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mMenu.performItemAction(mAdapter.getItem(position), this, 0);
}
@Override
public boolean flagActionItems() {
return false;
}
public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
return false;
}
public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
return false;
}
public void saveHierarchyState(Bundle outState) {
SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
if (mMenuView != null) {
((View) mMenuView).saveHierarchyState(viewStates);
}
outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
}
public void restoreHierarchyState(Bundle inState) {
SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
if (viewStates != null) {
((View) mMenuView).restoreHierarchyState(viewStates);
}
}
public void setId(int id) {
mId = id;
}
@Override
public int getId() {
return mId;
}
@Override
public Parcelable onSaveInstanceState() {
if (mMenuView == null) {
return null;
}
Bundle state = new Bundle();
saveHierarchyState(state);
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
restoreHierarchyState((Bundle) state);
}
private class MenuAdapter extends BaseAdapter {
private int mExpandedIndex = -1;
public MenuAdapter() {
findExpandedIndex();
}
public int getCount() {
ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
int count = items.size() - mItemIndexOffset;
if (mExpandedIndex < 0) {
return count;
}
return count - 1;
}
public MenuItemImpl getItem(int position) {
ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
position += mItemIndexOffset;
if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
position++;
}
return items.get(position);
}
public long getItemId(int position) {
// Since a menu item's ID is optional, we'll use the position as an
// ID for the item in the AdapterView
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(mItemLayoutRes, parent, false);
}
MenuView.ItemView itemView = (MenuView.ItemView) convertView;
itemView.initialize(getItem(position), 0);
return convertView;
}
void findExpandedIndex() {
final MenuItemImpl expandedItem = mMenu.getExpandedItem();
if (expandedItem != null) {
final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
final int count = items.size();
for (int i = 0; i < count; i++) {
final MenuItemImpl item = items.get(i);
if (item == expandedItem) {
mExpandedIndex = i;
return;
}
}
}
mExpandedIndex = -1;
}
@Override
public void notifyDataSetChanged() {
findExpandedIndex();
super.notifyDataSetChanged();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,172 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.IBinder;
import android.support.v7.appcompat.R;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
/**
* Helper for menus that appear as Dialogs (context and submenus).
*
* @hide
*/
public class MenuDialogHelper implements DialogInterface.OnKeyListener,
DialogInterface.OnClickListener,
DialogInterface.OnDismissListener,
MenuPresenter.Callback {
private MenuBuilder mMenu;
private AlertDialog mDialog;
ListMenuPresenter mPresenter;
private MenuPresenter.Callback mPresenterCallback;
public MenuDialogHelper(MenuBuilder menu) {
mMenu = menu;
}
/**
* Shows menu as a dialog.
*
* @param windowToken Optional token to assign to the window.
*/
public void show(IBinder windowToken) {
// Many references to mMenu, create local reference
final MenuBuilder menu = mMenu;
// Get the builder for the dialog
final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext());
// Need to force Light Menu theme as list_menu_item_layout is usually against a dark bg and
// AlertDialog's bg is white
mPresenter = new ListMenuPresenter(R.layout.abc_list_menu_item_layout,
R.style.Theme_AppCompat_CompactMenu);
mPresenter.setCallback(this);
mMenu.addMenuPresenter(mPresenter);
builder.setAdapter(mPresenter.getAdapter(), this);
// Set the title
final View headerView = menu.getHeaderView();
if (headerView != null) {
// Menu's client has given a custom header view, use it
builder.setCustomTitle(headerView);
} else {
// Otherwise use the (text) title and icon
builder.setIcon(menu.getHeaderIcon()).setTitle(menu.getHeaderTitle());
}
// Set the key listener
builder.setOnKeyListener(this);
// Show the menu
mDialog = builder.create();
mDialog.setOnDismissListener(this);
WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
if (windowToken != null) {
lp.token = windowToken;
}
lp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
mDialog.show();
}
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_BACK) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0) {
Window win = mDialog.getWindow();
if (win != null) {
View decor = win.getDecorView();
if (decor != null) {
KeyEvent.DispatcherState ds = decor.getKeyDispatcherState();
if (ds != null) {
ds.startTracking(event, this);
return true;
}
}
}
} else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()) {
Window win = mDialog.getWindow();
if (win != null) {
View decor = win.getDecorView();
if (decor != null) {
KeyEvent.DispatcherState ds = decor.getKeyDispatcherState();
if (ds != null && ds.isTracking(event)) {
mMenu.close(true);
dialog.dismiss();
return true;
}
}
}
}
}
// Menu shortcut matching
return mMenu.performShortcut(keyCode, event, 0);
}
public void setPresenterCallback(MenuPresenter.Callback cb) {
mPresenterCallback = cb;
}
/**
* Dismisses the menu's dialog.
*
* @see Dialog#dismiss()
*/
public void dismiss() {
if (mDialog != null) {
mDialog.dismiss();
}
}
@Override
public void onDismiss(DialogInterface dialog) {
mPresenter.onCloseMenu(mMenu, true);
}
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
if (allMenusAreClosing || menu == mMenu) {
dismiss();
}
if (mPresenterCallback != null) {
mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
}
}
@Override
public boolean onOpenSubMenu(MenuBuilder subMenu) {
if (mPresenterCallback != null) {
return mPresenterCallback.onOpenSubMenu(subMenu);
}
return false;
}
public void onClick(DialogInterface dialog, int which) {
mMenu.performItemAction((MenuItemImpl) mPresenter.getAdapter().getItem(which), 0);
}
}

View file

@ -0,0 +1,743 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ActionProvider;
import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.internal.widget.TintManager;
import android.util.Log;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewDebug;
import android.widget.LinearLayout;
/**
* @hide
*/
public final class MenuItemImpl implements SupportMenuItem {
private static final String TAG = "MenuItemImpl";
private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER |
SHOW_AS_ACTION_IF_ROOM |
SHOW_AS_ACTION_ALWAYS;
private final int mId;
private final int mGroup;
private final int mCategoryOrder;
private final int mOrdering;
private CharSequence mTitle;
private CharSequence mTitleCondensed;
private Intent mIntent;
private char mShortcutNumericChar;
private char mShortcutAlphabeticChar;
/** The icon's drawable which is only created as needed */
private Drawable mIconDrawable;
/**
* The icon's resource ID which is used to get the Drawable when it is
* needed (if the Drawable isn't already obtained--only one of the two is
* needed).
*/
private int mIconResId = NO_ICON;
/** The menu to which this item belongs */
private MenuBuilder mMenu;
/** If this item should launch a sub menu, this is the sub menu to launch */
private SubMenuBuilder mSubMenu;
private Runnable mItemCallback;
private SupportMenuItem.OnMenuItemClickListener mClickListener;
private int mFlags = ENABLED;
private static final int CHECKABLE = 0x00000001;
private static final int CHECKED = 0x00000002;
private static final int EXCLUSIVE = 0x00000004;
private static final int HIDDEN = 0x00000008;
private static final int ENABLED = 0x00000010;
private static final int IS_ACTION = 0x00000020;
private int mShowAsAction = SHOW_AS_ACTION_NEVER;
private View mActionView;
private ActionProvider mActionProvider;
private MenuItemCompat.OnActionExpandListener mOnActionExpandListener;
private boolean mIsActionViewExpanded = false;
/** Used for the icon resource ID if this item does not have an icon */
static final int NO_ICON = 0;
/**
* Current use case is for context menu: Extra information linked to the
* View that added this item to the context menu.
*/
private ContextMenuInfo mMenuInfo;
private static String sPrependShortcutLabel;
private static String sEnterShortcutLabel;
private static String sDeleteShortcutLabel;
private static String sSpaceShortcutLabel;
/**
* Instantiates this menu item.
*
* @param menu
* @param group Item ordering grouping control. The item will be added after
* all other items whose order is <= this number, and before any
* that are larger than it. This can also be used to define
* groups of items for batch state changes. Normally use 0.
* @param id Unique item ID. Use 0 if you do not need a unique ID.
* @param categoryOrder The ordering for this item.
* @param title The text to display for the item.
*/
MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
CharSequence title, int showAsAction) {
/*if (sPrependShortcutLabel == null) {
// This is instantiated from the UI thread, so no chance of sync issues
sPrependShortcutLabel = menu.getContext().getResources().getString(
com.android.internal.R.string.prepend_shortcut_label);
sEnterShortcutLabel = menu.getContext().getResources().getString(
com.android.internal.R.string.menu_enter_shortcut_label);
sDeleteShortcutLabel = menu.getContext().getResources().getString(
com.android.internal.R.string.menu_delete_shortcut_label);
sSpaceShortcutLabel = menu.getContext().getResources().getString(
com.android.internal.R.string.menu_space_shortcut_label);
}*/
mMenu = menu;
mId = id;
mGroup = group;
mCategoryOrder = categoryOrder;
mOrdering = ordering;
mTitle = title;
mShowAsAction = showAsAction;
}
/**
* Invokes the item by calling various listeners or callbacks.
*
* @return true if the invocation was handled, false otherwise
*/
public boolean invoke() {
if (mClickListener != null && mClickListener.onMenuItemClick(this)) {
return true;
}
if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
return true;
}
if (mItemCallback != null) {
mItemCallback.run();
return true;
}
if (mIntent != null) {
try {
mMenu.getContext().startActivity(mIntent);
return true;
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Can't find activity to handle intent; ignoring", e);
}
}
if (mActionProvider != null && mActionProvider.onPerformDefaultAction()) {
return true;
}
return false;
}
@Override
public boolean isEnabled() {
return (mFlags & ENABLED) != 0;
}
@Override
public MenuItem setEnabled(boolean enabled) {
if (enabled) {
mFlags |= ENABLED;
} else {
mFlags &= ~ENABLED;
}
mMenu.onItemsChanged(false);
return this;
}
@Override
public int getGroupId() {
return mGroup;
}
@Override
@ViewDebug.CapturedViewProperty
public int getItemId() {
return mId;
}
@Override
public int getOrder() {
return mCategoryOrder;
}
public int getOrdering() {
return mOrdering;
}
@Override
public Intent getIntent() {
return mIntent;
}
@Override
public MenuItem setIntent(Intent intent) {
mIntent = intent;
return this;
}
Runnable getCallback() {
return mItemCallback;
}
public MenuItem setCallback(Runnable callback) {
mItemCallback = callback;
return this;
}
@Override
public char getAlphabeticShortcut() {
return mShortcutAlphabeticChar;
}
@Override
public MenuItem setAlphabeticShortcut(char alphaChar) {
if (mShortcutAlphabeticChar == alphaChar) {
return this;
}
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
mMenu.onItemsChanged(false);
return this;
}
@Override
public char getNumericShortcut() {
return mShortcutNumericChar;
}
@Override
public MenuItem setNumericShortcut(char numericChar) {
if (mShortcutNumericChar == numericChar) {
return this;
}
mShortcutNumericChar = numericChar;
mMenu.onItemsChanged(false);
return this;
}
@Override
public MenuItem setShortcut(char numericChar, char alphaChar) {
mShortcutNumericChar = numericChar;
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
mMenu.onItemsChanged(false);
return this;
}
/**
* @return The active shortcut (based on QWERTY-mode of the menu).
*/
char getShortcut() {
return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
}
/**
* @return The label to show for the shortcut. This includes the chording key (for example
* 'Menu+a'). Also, any non-human readable characters should be human readable (for
* example 'Menu+enter').
*/
String getShortcutLabel() {
char shortcut = getShortcut();
if (shortcut == 0) {
return "";
}
StringBuilder sb = new StringBuilder(sPrependShortcutLabel);
switch (shortcut) {
case '\n':
sb.append(sEnterShortcutLabel);
break;
case '\b':
sb.append(sDeleteShortcutLabel);
break;
case ' ':
sb.append(sSpaceShortcutLabel);
break;
default:
sb.append(shortcut);
break;
}
return sb.toString();
}
/**
* @return Whether this menu item should be showing shortcuts (depends on
* whether the menu should show shortcuts and whether this item has
* a shortcut defined)
*/
boolean shouldShowShortcut() {
// Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
return mMenu.isShortcutsVisible() && (getShortcut() != 0);
}
@Override
public SubMenu getSubMenu() {
return mSubMenu;
}
@Override
public boolean hasSubMenu() {
return mSubMenu != null;
}
void setSubMenu(SubMenuBuilder subMenu) {
mSubMenu = subMenu;
subMenu.setHeaderTitle(getTitle());
}
@Override
@ViewDebug.CapturedViewProperty
public CharSequence getTitle() {
return mTitle;
}
/**
* Gets the title for a particular {@link MenuView.ItemView}
*
* @param itemView The ItemView that is receiving the title
* @return Either the title or condensed title based on what the ItemView prefers
*/
CharSequence getTitleForItemView(MenuView.ItemView itemView) {
return ((itemView != null) && itemView.prefersCondensedTitle())
? getTitleCondensed()
: getTitle();
}
@Override
public MenuItem setTitle(CharSequence title) {
mTitle = title;
mMenu.onItemsChanged(false);
if (mSubMenu != null) {
mSubMenu.setHeaderTitle(title);
}
return this;
}
@Override
public MenuItem setTitle(int title) {
return setTitle(mMenu.getContext().getString(title));
}
@Override
public CharSequence getTitleCondensed() {
final CharSequence ctitle = mTitleCondensed != null ? mTitleCondensed : mTitle;
if (Build.VERSION.SDK_INT < 18 && ctitle != null && !(ctitle instanceof String)) {
// For devices pre-JB-MR2, where we have a non-String CharSequence, we need to
// convert this to a String so that EventLog.writeEvent() does not throw an exception
// in Activity.onMenuItemSelected()
return ctitle.toString();
} else {
// Else, we just return the condensed title
return ctitle;
}
}
@Override
public MenuItem setTitleCondensed(CharSequence title) {
mTitleCondensed = title;
// Could use getTitle() in the loop below, but just cache what it would do here
if (title == null) {
title = mTitle;
}
mMenu.onItemsChanged(false);
return this;
}
@Override
public Drawable getIcon() {
if (mIconDrawable != null) {
return mIconDrawable;
}
if (mIconResId != NO_ICON) {
Drawable icon = TintManager.getDrawable(mMenu.getContext(), mIconResId);
mIconResId = NO_ICON;
mIconDrawable = icon;
return icon;
}
return null;
}
@Override
public MenuItem setIcon(Drawable icon) {
mIconResId = NO_ICON;
mIconDrawable = icon;
mMenu.onItemsChanged(false);
return this;
}
@Override
public MenuItem setIcon(int iconResId) {
mIconDrawable = null;
mIconResId = iconResId;
// If we have a view, we need to push the Drawable to them
mMenu.onItemsChanged(false);
return this;
}
@Override
public boolean isCheckable() {
return (mFlags & CHECKABLE) == CHECKABLE;
}
@Override
public MenuItem setCheckable(boolean checkable) {
final int oldFlags = mFlags;
mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
if (oldFlags != mFlags) {
mMenu.onItemsChanged(false);
}
return this;
}
public void setExclusiveCheckable(boolean exclusive) {
mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
}
public boolean isExclusiveCheckable() {
return (mFlags & EXCLUSIVE) != 0;
}
@Override
public boolean isChecked() {
return (mFlags & CHECKED) == CHECKED;
}
@Override
public MenuItem setChecked(boolean checked) {
if ((mFlags & EXCLUSIVE) != 0) {
// Call the method on the Menu since it knows about the others in this
// exclusive checkable group
mMenu.setExclusiveItemChecked(this);
} else {
setCheckedInt(checked);
}
return this;
}
void setCheckedInt(boolean checked) {
final int oldFlags = mFlags;
mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
if (oldFlags != mFlags) {
mMenu.onItemsChanged(false);
}
}
@Override
public boolean isVisible() {
if (mActionProvider != null && mActionProvider.overridesItemVisibility()) {
return (mFlags & HIDDEN) == 0 && mActionProvider.isVisible();
}
return (mFlags & HIDDEN) == 0;
}
/**
* Changes the visibility of the item. This method DOES NOT notify the parent menu of a change
* in this item, so this should only be called from methods that will eventually trigger this
* change. If unsure, use {@link #setVisible(boolean)} instead.
*
* @param shown Whether to show (true) or hide (false).
* @return Whether the item's shown state was changed
*/
boolean setVisibleInt(boolean shown) {
final int oldFlags = mFlags;
mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
return oldFlags != mFlags;
}
@Override
public MenuItem setVisible(boolean shown) {
// Try to set the shown state to the given state. If the shown state was changed
// (i.e. the previous state isn't the same as given state), notify the parent menu that
// the shown state has changed for this item
if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
return this;
}
@Override
public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) {
mClickListener = clickListener;
return this;
}
@Override
public String toString() {
return mTitle.toString();
}
void setMenuInfo(ContextMenuInfo menuInfo) {
mMenuInfo = menuInfo;
}
@Override
public ContextMenuInfo getMenuInfo() {
return mMenuInfo;
}
public void actionFormatChanged() {
mMenu.onItemActionRequestChanged(this);
}
/**
* @return Whether the menu should show icons for menu items.
*/
public boolean shouldShowIcon() {
return mMenu.getOptionalIconsVisible();
}
public boolean isActionButton() {
return (mFlags & IS_ACTION) == IS_ACTION;
}
public boolean requestsActionButton() {
return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM;
}
public boolean requiresActionButton() {
return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS;
}
public void setIsActionButton(boolean isActionButton) {
if (isActionButton) {
mFlags |= IS_ACTION;
} else {
mFlags &= ~IS_ACTION;
}
}
public boolean showsTextAsAction() {
return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT;
}
@Override
public void setShowAsAction(int actionEnum) {
switch (actionEnum & SHOW_AS_ACTION_MASK) {
case SHOW_AS_ACTION_ALWAYS:
case SHOW_AS_ACTION_IF_ROOM:
case SHOW_AS_ACTION_NEVER:
// Looks good!
break;
default:
// Mutually exclusive options selected!
throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM,"
+ " and SHOW_AS_ACTION_NEVER are mutually exclusive.");
}
mShowAsAction = actionEnum;
mMenu.onItemActionRequestChanged(this);
}
@Override
public SupportMenuItem setActionView(View view) {
mActionView = view;
mActionProvider = null;
if (view != null && view.getId() == View.NO_ID && mId > 0) {
view.setId(mId);
}
mMenu.onItemActionRequestChanged(this);
return this;
}
@Override
public SupportMenuItem setActionView(int resId) {
final Context context = mMenu.getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
setActionView(inflater.inflate(resId, new LinearLayout(context), false));
return this;
}
@Override
public View getActionView() {
if (mActionView != null) {
return mActionView;
} else if (mActionProvider != null) {
mActionView = mActionProvider.onCreateActionView(this);
return mActionView;
} else {
return null;
}
}
@Override
public MenuItem setActionProvider(android.view.ActionProvider actionProvider) {
throw new UnsupportedOperationException(
"This is not supported, use MenuItemCompat.setActionProvider()");
}
@Override
public android.view.ActionProvider getActionProvider() {
throw new UnsupportedOperationException(
"This is not supported, use MenuItemCompat.getActionProvider()");
}
@Override
public ActionProvider getSupportActionProvider() {
return mActionProvider;
}
@Override
public SupportMenuItem setSupportActionProvider(ActionProvider actionProvider) {
if (mActionProvider != null) {
mActionProvider.setVisibilityListener(null);
}
mActionView = null;
mActionProvider = actionProvider;
mMenu.onItemsChanged(true); // Measurement can be changed
if (mActionProvider != null) {
mActionProvider.setVisibilityListener(new ActionProvider.VisibilityListener() {
@Override
public void onActionProviderVisibilityChanged(boolean isVisible) {
mMenu.onItemVisibleChanged(MenuItemImpl.this);
}
});
}
return this;
}
@Override
public SupportMenuItem setShowAsActionFlags(int actionEnum) {
setShowAsAction(actionEnum);
return this;
}
@Override
public boolean expandActionView() {
if (!hasCollapsibleActionView()) {
return false;
}
if (mOnActionExpandListener == null ||
mOnActionExpandListener.onMenuItemActionExpand(this)) {
return mMenu.expandItemActionView(this);
}
return false;
}
@Override
public boolean collapseActionView() {
if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) {
return false;
}
if (mActionView == null) {
// We're already collapsed if we have no action view.
return true;
}
if (mOnActionExpandListener == null ||
mOnActionExpandListener.onMenuItemActionCollapse(this)) {
return mMenu.collapseItemActionView(this);
}
return false;
}
@Override
public SupportMenuItem setSupportOnActionExpandListener(
MenuItemCompat.OnActionExpandListener listener) {
mOnActionExpandListener = listener;
return this;
}
public boolean hasCollapsibleActionView() {
if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0) {
if (mActionView == null && mActionProvider != null) {
mActionView = mActionProvider.onCreateActionView(this);
}
return mActionView != null;
}
return false;
}
public void setActionViewExpanded(boolean isExpanded) {
mIsActionViewExpanded = isExpanded;
mMenu.onItemsChanged(false);
}
@Override
public boolean isActionViewExpanded() {
return mIsActionViewExpanded;
}
@Override
public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) {
throw new UnsupportedOperationException(
"This is not supported, use MenuItemCompat.setOnActionExpandListener()");
}
}

View file

@ -0,0 +1,401 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.view.ActionProvider;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.view.CollapsibleActionView;
import android.util.Log;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.widget.FrameLayout;
import java.lang.reflect.Method;
/**
* Wraps a support {@link SupportMenuItem} as a framework {@link android.view.MenuItem}
* @hide
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class MenuItemWrapperICS extends BaseMenuWrapper<SupportMenuItem> implements MenuItem {
static final String LOG_TAG = "MenuItemWrapper";
// Reflection Method to call setExclusiveCheckable
private Method mSetExclusiveCheckableMethod;
MenuItemWrapperICS(Context context, SupportMenuItem object) {
super(context, object);
}
@Override
public int getItemId() {
return mWrappedObject.getItemId();
}
@Override
public int getGroupId() {
return mWrappedObject.getGroupId();
}
@Override
public int getOrder() {
return mWrappedObject.getOrder();
}
@Override
public MenuItem setTitle(CharSequence title) {
mWrappedObject.setTitle(title);
return this;
}
@Override
public MenuItem setTitle(int title) {
mWrappedObject.setTitle(title);
return this;
}
@Override
public CharSequence getTitle() {
return mWrappedObject.getTitle();
}
@Override
public MenuItem setTitleCondensed(CharSequence title) {
mWrappedObject.setTitleCondensed(title);
return this;
}
@Override
public CharSequence getTitleCondensed() {
return mWrappedObject.getTitleCondensed();
}
@Override
public MenuItem setIcon(Drawable icon) {
mWrappedObject.setIcon(icon);
return this;
}
@Override
public MenuItem setIcon(int iconRes) {
mWrappedObject.setIcon(iconRes);
return this;
}
@Override
public Drawable getIcon() {
return mWrappedObject.getIcon();
}
@Override
public MenuItem setIntent(Intent intent) {
mWrappedObject.setIntent(intent);
return this;
}
@Override
public Intent getIntent() {
return mWrappedObject.getIntent();
}
@Override
public MenuItem setShortcut(char numericChar, char alphaChar) {
mWrappedObject.setShortcut(numericChar, alphaChar);
return this;
}
@Override
public MenuItem setNumericShortcut(char numericChar) {
mWrappedObject.setNumericShortcut(numericChar);
return this;
}
@Override
public char getNumericShortcut() {
return mWrappedObject.getNumericShortcut();
}
@Override
public MenuItem setAlphabeticShortcut(char alphaChar) {
mWrappedObject.setAlphabeticShortcut(alphaChar);
return this;
}
@Override
public char getAlphabeticShortcut() {
return mWrappedObject.getAlphabeticShortcut();
}
@Override
public MenuItem setCheckable(boolean checkable) {
mWrappedObject.setCheckable(checkable);
return this;
}
@Override
public boolean isCheckable() {
return mWrappedObject.isCheckable();
}
@Override
public MenuItem setChecked(boolean checked) {
mWrappedObject.setChecked(checked);
return this;
}
@Override
public boolean isChecked() {
return mWrappedObject.isChecked();
}
@Override
public MenuItem setVisible(boolean visible) {
return mWrappedObject.setVisible(visible);
}
@Override
public boolean isVisible() {
return mWrappedObject.isVisible();
}
@Override
public MenuItem setEnabled(boolean enabled) {
mWrappedObject.setEnabled(enabled);
return this;
}
@Override
public boolean isEnabled() {
return mWrappedObject.isEnabled();
}
@Override
public boolean hasSubMenu() {
return mWrappedObject.hasSubMenu();
}
@Override
public SubMenu getSubMenu() {
return getSubMenuWrapper(mWrappedObject.getSubMenu());
}
@Override
public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
mWrappedObject.setOnMenuItemClickListener(menuItemClickListener != null ?
new OnMenuItemClickListenerWrapper(menuItemClickListener) : null);
return this;
}
@Override
public ContextMenu.ContextMenuInfo getMenuInfo() {
return mWrappedObject.getMenuInfo();
}
@Override
public void setShowAsAction(int actionEnum) {
mWrappedObject.setShowAsAction(actionEnum);
}
@Override
public MenuItem setShowAsActionFlags(int actionEnum) {
mWrappedObject.setShowAsActionFlags(actionEnum);
return this;
}
@Override
public MenuItem setActionView(View view) {
if (view instanceof android.view.CollapsibleActionView) {
view = new CollapsibleActionViewWrapper(view);
}
mWrappedObject.setActionView(view);
return this;
}
@Override
public MenuItem setActionView(int resId) {
// Make framework menu item inflate the view
mWrappedObject.setActionView(resId);
View actionView = mWrappedObject.getActionView();
if (actionView instanceof android.view.CollapsibleActionView) {
// If the inflated Action View is support-collapsible, wrap it
mWrappedObject.setActionView(new CollapsibleActionViewWrapper(actionView));
}
return this;
}
@Override
public View getActionView() {
View actionView = mWrappedObject.getActionView();
if (actionView instanceof CollapsibleActionViewWrapper) {
return ((CollapsibleActionViewWrapper) actionView).getWrappedView();
}
return actionView;
}
@Override
public MenuItem setActionProvider(android.view.ActionProvider provider) {
mWrappedObject.setSupportActionProvider(
provider != null ? createActionProviderWrapper(provider) : null);
return this;
}
@Override
public android.view.ActionProvider getActionProvider() {
ActionProvider provider = mWrappedObject.getSupportActionProvider();
if (provider instanceof ActionProviderWrapper) {
return ((ActionProviderWrapper) provider).mInner;
}
return null;
}
@Override
public boolean expandActionView() {
return mWrappedObject.expandActionView();
}
@Override
public boolean collapseActionView() {
return mWrappedObject.collapseActionView();
}
@Override
public boolean isActionViewExpanded() {
return mWrappedObject.isActionViewExpanded();
}
@Override
public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) {
mWrappedObject.setSupportOnActionExpandListener(listener != null ?
new OnActionExpandListenerWrapper(listener) : null);
return this;
}
public void setExclusiveCheckable(boolean checkable) {
try {
if (mSetExclusiveCheckableMethod == null) {
mSetExclusiveCheckableMethod = mWrappedObject.getClass()
.getDeclaredMethod("setExclusiveCheckable", Boolean.TYPE);
}
mSetExclusiveCheckableMethod.invoke(mWrappedObject, checkable);
} catch (Exception e) {
Log.w(LOG_TAG, "Error while calling setExclusiveCheckable", e);
}
}
ActionProviderWrapper createActionProviderWrapper(android.view.ActionProvider provider) {
return new ActionProviderWrapper(mContext, provider);
}
private class OnMenuItemClickListenerWrapper extends BaseWrapper<OnMenuItemClickListener>
implements android.view.MenuItem.OnMenuItemClickListener {
OnMenuItemClickListenerWrapper(OnMenuItemClickListener object) {
super(object);
}
@Override
public boolean onMenuItemClick(android.view.MenuItem item) {
return mWrappedObject.onMenuItemClick(getMenuItemWrapper(item));
}
}
private class OnActionExpandListenerWrapper extends BaseWrapper<MenuItem.OnActionExpandListener>
implements MenuItemCompat.OnActionExpandListener {
OnActionExpandListenerWrapper(MenuItem.OnActionExpandListener object) {
super(object);
}
@Override
public boolean onMenuItemActionExpand(android.view.MenuItem item) {
return mWrappedObject.onMenuItemActionExpand(getMenuItemWrapper(item));
}
@Override
public boolean onMenuItemActionCollapse(android.view.MenuItem item) {
return mWrappedObject.onMenuItemActionCollapse(getMenuItemWrapper(item));
}
}
class ActionProviderWrapper extends android.support.v4.view.ActionProvider {
final android.view.ActionProvider mInner;
public ActionProviderWrapper(Context context, android.view.ActionProvider inner) {
super(context);
mInner = inner;
}
@Override
public View onCreateActionView() {
return mInner.onCreateActionView();
}
@Override
public boolean onPerformDefaultAction() {
return mInner.onPerformDefaultAction();
}
@Override
public boolean hasSubMenu() {
return mInner.hasSubMenu();
}
@Override
public void onPrepareSubMenu(android.view.SubMenu subMenu) {
mInner.onPrepareSubMenu(getSubMenuWrapper(subMenu));
}
}
/**
* Wrap a support {@link android.support.v7.view.CollapsibleActionView} into a framework
* {@link android.view.CollapsibleActionView}.
*/
static class CollapsibleActionViewWrapper extends FrameLayout
implements CollapsibleActionView {
final android.view.CollapsibleActionView mWrappedView;
CollapsibleActionViewWrapper(View actionView) {
super(actionView.getContext());
mWrappedView = (android.view.CollapsibleActionView) actionView;
addView(actionView);
}
@Override
public void onActionViewExpanded() {
mWrappedView.onActionViewExpanded();
}
@Override
public void onActionViewCollapsed() {
mWrappedView.onActionViewCollapsed();
}
View getWrappedView() {
return (View) mWrappedView;
}
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.view.ActionProvider;
import android.view.MenuItem;
import android.view.View;
/**
* Wraps a support {@link SupportMenuItem} as a framework {@link android.view.MenuItem}
* @hide
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
class MenuItemWrapperJB extends MenuItemWrapperICS {
MenuItemWrapperJB(Context context, SupportMenuItem object) {
super(context, object);
}
@Override
ActionProviderWrapper createActionProviderWrapper(android.view.ActionProvider provider) {
return new ActionProviderWrapperJB(mContext, provider);
}
class ActionProviderWrapperJB extends ActionProviderWrapper
implements android.view.ActionProvider.VisibilityListener {
ActionProvider.VisibilityListener mListener;
public ActionProviderWrapperJB(Context context, android.view.ActionProvider inner) {
super(context, inner);
}
@Override
public View onCreateActionView(MenuItem forItem) {
return mInner.onCreateActionView(forItem);
}
@Override
public boolean overridesItemVisibility() {
return mInner.overridesItemVisibility();
}
@Override
public boolean isVisible() {
return mInner.isVisible();
}
@Override
public void refreshVisibility() {
mInner.refreshVisibility();
}
@Override
public void setVisibilityListener(ActionProvider.VisibilityListener listener) {
mListener = listener;
mInner.setVisibilityListener(listener != null ? this : null);
}
@Override
public void onActionProviderVisibilityChanged(boolean isVisible) {
if (mListener != null) {
mListener.onActionProviderVisibilityChanged(isVisible);
}
}
}
}

View file

@ -0,0 +1,404 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.content.res.Resources;
import android.os.Parcelable;
import android.support.v7.appcompat.R;
import android.support.v7.widget.ListPopupWindow;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ListAdapter;
import android.widget.PopupWindow;
import java.util.ArrayList;
/**
* Presents a menu as a small, simple popup anchored to another view.
*
* @hide
*/
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
MenuPresenter {
private static final String TAG = "MenuPopupHelper";
static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout;
private final Context mContext;
private final LayoutInflater mInflater;
private final MenuBuilder mMenu;
private final MenuAdapter mAdapter;
private final boolean mOverflowOnly;
private final int mPopupMaxWidth;
private final int mPopupStyleAttr;
private final int mPopupStyleRes;
private View mAnchorView;
private ListPopupWindow mPopup;
private ViewTreeObserver mTreeObserver;
private Callback mPresenterCallback;
boolean mForceShowIcon;
private ViewGroup mMeasureParent;
/** Whether the cached content width value is valid. */
private boolean mHasContentWidth;
/** Cached content width from {@link #measureContentWidth}. */
private int mContentWidth;
private int mDropDownGravity = Gravity.NO_GRAVITY;
public MenuPopupHelper(Context context, MenuBuilder menu) {
this(context, menu, null, false, R.attr.popupMenuStyle);
}
public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
this(context, menu, anchorView, false, R.attr.popupMenuStyle);
}
public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
boolean overflowOnly, int popupStyleAttr) {
this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
}
public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
mContext = context;
mInflater = LayoutInflater.from(context);
mMenu = menu;
mAdapter = new MenuAdapter(mMenu);
mOverflowOnly = overflowOnly;
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
final Resources res = context.getResources();
mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
mAnchorView = anchorView;
// Present the menu using our context, not the menu builder's context.
menu.addMenuPresenter(this, context);
}
public void setAnchorView(View anchor) {
mAnchorView = anchor;
}
public void setForceShowIcon(boolean forceShow) {
mForceShowIcon = forceShow;
}
public void setGravity(int gravity) {
mDropDownGravity = gravity;
}
public void show() {
if (!tryShow()) {
throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
}
}
public ListPopupWindow getPopup() {
return mPopup;
}
public boolean tryShow() {
mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
mPopup.setOnDismissListener(this);
mPopup.setOnItemClickListener(this);
mPopup.setAdapter(mAdapter);
mPopup.setModal(true);
View anchor = mAnchorView;
if (anchor != null) {
final boolean addGlobalListener = mTreeObserver == null;
mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
mPopup.setAnchorView(anchor);
mPopup.setDropDownGravity(mDropDownGravity);
} else {
return false;
}
if (!mHasContentWidth) {
mContentWidth = measureContentWidth();
mHasContentWidth = true;
}
mPopup.setContentWidth(mContentWidth);
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
mPopup.show();
mPopup.getListView().setOnKeyListener(this);
return true;
}
public void dismiss() {
if (isShowing()) {
mPopup.dismiss();
}
}
public void onDismiss() {
mPopup = null;
mMenu.close();
if (mTreeObserver != null) {
if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
mTreeObserver.removeGlobalOnLayoutListener(this);
mTreeObserver = null;
}
}
public boolean isShowing() {
return mPopup != null && mPopup.isShowing();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
MenuAdapter adapter = mAdapter;
adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
}
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
dismiss();
return true;
}
return false;
}
private int measureContentWidth() {
// Menus don't tend to be long, so this is more sane than it looks.
int maxWidth = 0;
View itemView = null;
int itemType = 0;
final ListAdapter adapter = mAdapter;
final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final int count = adapter.getCount();
for (int i = 0; i < count; i++) {
final int positionType = adapter.getItemViewType(i);
if (positionType != itemType) {
itemType = positionType;
itemView = null;
}
if (mMeasureParent == null) {
mMeasureParent = new FrameLayout(mContext);
}
itemView = adapter.getView(i, itemView, mMeasureParent);
itemView.measure(widthMeasureSpec, heightMeasureSpec);
final int itemWidth = itemView.getMeasuredWidth();
if (itemWidth >= mPopupMaxWidth) {
return mPopupMaxWidth;
} else if (itemWidth > maxWidth) {
maxWidth = itemWidth;
}
}
return maxWidth;
}
@Override
public void onGlobalLayout() {
if (isShowing()) {
final View anchor = mAnchorView;
if (anchor == null || !anchor.isShown()) {
dismiss();
} else if (isShowing()) {
// Recompute window size and position
mPopup.show();
}
}
}
@Override
public void initForMenu(Context context, MenuBuilder menu) {
// Don't need to do anything; we added as a presenter in the constructor.
}
@Override
public MenuView getMenuView(ViewGroup root) {
throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
}
@Override
public void updateMenuView(boolean cleared) {
mHasContentWidth = false;
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
@Override
public void setCallback(Callback cb) {
mPresenterCallback = cb;
}
@Override
public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
if (subMenu.hasVisibleItems()) {
MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView);
subPopup.setCallback(mPresenterCallback);
boolean preserveIconSpacing = false;
final int count = subMenu.size();
for (int i = 0; i < count; i++) {
MenuItem childItem = subMenu.getItem(i);
if (childItem.isVisible() && childItem.getIcon() != null) {
preserveIconSpacing = true;
break;
}
}
subPopup.setForceShowIcon(preserveIconSpacing);
if (subPopup.tryShow()) {
if (mPresenterCallback != null) {
mPresenterCallback.onOpenSubMenu(subMenu);
}
return true;
}
}
return false;
}
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
// Only care about the (sub)menu we're presenting.
if (menu != mMenu) return;
dismiss();
if (mPresenterCallback != null) {
mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
}
}
@Override
public boolean flagActionItems() {
return false;
}
public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
return false;
}
public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
return false;
}
@Override
public int getId() {
return 0;
}
@Override
public Parcelable onSaveInstanceState() {
return null;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
}
private class MenuAdapter extends BaseAdapter {
private MenuBuilder mAdapterMenu;
private int mExpandedIndex = -1;
public MenuAdapter(MenuBuilder menu) {
mAdapterMenu = menu;
findExpandedIndex();
}
public int getCount() {
ArrayList<MenuItemImpl> items = mOverflowOnly ?
mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
if (mExpandedIndex < 0) {
return items.size();
}
return items.size() - 1;
}
public MenuItemImpl getItem(int position) {
ArrayList<MenuItemImpl> items = mOverflowOnly ?
mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
position++;
}
return items.get(position);
}
public long getItemId(int position) {
// Since a menu item's ID is optional, we'll use the position as an
// ID for the item in the AdapterView
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
}
MenuView.ItemView itemView = (MenuView.ItemView) convertView;
if (mForceShowIcon) {
((ListMenuItemView) convertView).setForceShowIcon(true);
}
itemView.initialize(getItem(position), 0);
return convertView;
}
void findExpandedIndex() {
final MenuItemImpl expandedItem = mMenu.getExpandedItem();
if (expandedItem != null) {
final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
final int count = items.size();
for (int i = 0; i < count; i++) {
final MenuItemImpl item = items.get(i);
if (item == expandedItem) {
mExpandedIndex = i;
return;
}
}
}
mExpandedIndex = -1;
}
@Override
public void notifyDataSetChanged() {
findExpandedIndex();
super.notifyDataSetChanged();
}
}
}

View file

@ -0,0 +1,153 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.os.Parcelable;
import android.view.ViewGroup;
/**
* A MenuPresenter is responsible for building views for a Menu object. It takes over some
* responsibility from the old style monolithic MenuBuilder class.
*
* @hide
*/
public interface MenuPresenter {
/**
* Called by menu implementation to notify another component of open/close events.
*
* @hide
*/
public interface Callback {
/**
* Called when a menu is closing.
* @param menu
* @param allMenusAreClosing
*/
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
/**
* Called when a submenu opens. Useful for notifying the application
* of menu state so that it does not attempt to hide the action bar
* while a submenu is open or similar.
*
* @param subMenu Submenu currently being opened
* @return true if the Callback will handle presenting the submenu, false if
* the presenter should attempt to do so.
*/
public boolean onOpenSubMenu(MenuBuilder subMenu);
}
/**
* Initialize this presenter for the given context and menu.
* This method is called by MenuBuilder when a presenter is
* added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
*
* @param context Context for this presenter; used for view creation and resource management
* @param menu Menu to host
*/
public void initForMenu(Context context, MenuBuilder menu);
/**
* Retrieve a MenuView to display the menu specified in
* {@link #initForMenu(Context, MenuBuilder)}.
*
* @param root Intended parent of the MenuView.
* @return A freshly created MenuView.
*/
public MenuView getMenuView(ViewGroup root);
/**
* Update the menu UI in response to a change. Called by
* MenuBuilder during the normal course of operation.
*
* @param cleared true if the menu was entirely cleared
*/
public void updateMenuView(boolean cleared);
/**
* Set a callback object that will be notified of menu events
* related to this specific presentation.
* @param cb Callback that will be notified of future events
*/
public void setCallback(Callback cb);
/**
* Called by Menu implementations to indicate that a submenu item
* has been selected. An active Callback should be notified, and
* if applicable the presenter should present the submenu.
*
* @param subMenu SubMenu being opened
* @return true if the the event was handled, false otherwise.
*/
public boolean onSubMenuSelected(SubMenuBuilder subMenu);
/**
* Called by Menu implementations to indicate that a menu or submenu is
* closing. Presenter implementations should close the representation
* of the menu indicated as necessary and notify a registered callback.
*
* @param menu Menu or submenu that is closing.
* @param allMenusAreClosing True if all associated menus are closing.
*/
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
/**
* Called by Menu implementations to flag items that will be shown as actions.
* @return true if this presenter changed the action status of any items.
*/
public boolean flagActionItems();
/**
* Called when a menu item with a collapsable action view should expand its action view.
*
* @param menu Menu containing the item to be expanded
* @param item Item to be expanded
* @return true if this presenter expanded the action view, false otherwise.
*/
public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item);
/**
* Called when a menu item with a collapsable action view should collapse its action view.
*
* @param menu Menu containing the item to be collapsed
* @param item Item to be collapsed
* @return true if this presenter collapsed the action view, false otherwise.
*/
public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item);
/**
* Returns an ID for determining how to save/restore instance state.
* @return a valid ID value.
*/
public int getId();
/**
* Returns a Parcelable describing the current state of the presenter.
* It will be passed to the {@link #onRestoreInstanceState(Parcelable)}
* method of the presenter sharing the same ID later.
* @return The saved instance state
*/
public Parcelable onSaveInstanceState();
/**
* Supplies the previously saved instance state to be restored.
* @param state The previously saved instance state
*/
public void onRestoreInstanceState(Parcelable state);
}

View file

@ -0,0 +1,120 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.graphics.drawable.Drawable;
/**
* Minimal interface for a menu view. {@link #initialize(MenuBuilder)} must be called for the
* menu to be functional.
*
* @hide
*/
public interface MenuView {
/**
* Initializes the menu to the given menu. This should be called after the
* view is inflated.
*
* @param menu The menu that this MenuView should display.
*/
public void initialize(MenuBuilder menu);
/**
* Returns the default animations to be used for this menu when entering/exiting.
* @return A resource ID for the default animations to be used for this menu.
*/
public int getWindowAnimations();
/**
* Minimal interface for a menu item view. {@link #initialize(MenuItemImpl, int)} must be called
* for the item to be functional.
*/
public interface ItemView {
/**
* Initializes with the provided MenuItemData. This should be called after the view is
* inflated.
* @param itemData The item that this ItemView should display.
* @param menuType The type of this menu, one of
* {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
* {@link MenuBuilder#TYPE_DIALOG}).
*/
public void initialize(MenuItemImpl itemData, int menuType);
/**
* Gets the item data that this view is displaying.
* @return the item data, or null if there is not one
*/
public MenuItemImpl getItemData();
/**
* Sets the title of the item view.
* @param title The title to set.
*/
public void setTitle(CharSequence title);
/**
* Sets the enabled state of the item view.
* @param enabled Whether the item view should be enabled.
*/
public void setEnabled(boolean enabled);
/**
* Displays the checkbox for the item view. This does not ensure the item view will be
* checked, for that use {@link #setChecked}.
* @param checkable Whether to display the checkbox or to hide it
*/
public void setCheckable(boolean checkable);
/**
* Checks the checkbox for the item view. If the checkbox is hidden, it will NOT be
* made visible, call {@link #setCheckable(boolean)} for that.
* @param checked Whether the checkbox should be checked
*/
public void setChecked(boolean checked);
/**
* Sets the shortcut for the item.
* @param showShortcut Whether a shortcut should be shown(if false, the value of
* shortcutKey should be ignored).
* @param shortcutKey The shortcut key that should be shown on the ItemView.
*/
public void setShortcut(boolean showShortcut, char shortcutKey);
/**
* Set the icon of this item view.
* @param icon The icon of this item. null to hide the icon.
*/
public void setIcon(Drawable icon);
/**
* Whether this item view prefers displaying the condensed title rather
* than the normal title. If a condensed title is not available, the
* normal title will be used.
*
* @return Whether this item view prefers displaying the condensed
* title.
*/
public boolean prefersCondensedTitle();
/**
* Whether this item view shows an icon.
*
* @return Whether this item view shows an icon.
*/
public boolean showsIcon();
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.os.Build;
import android.support.v4.internal.view.SupportMenu;
import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.internal.view.SupportSubMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
/**
* @hide
*/
public final class MenuWrapperFactory {
private MenuWrapperFactory() {
}
public static Menu wrapSupportMenu(Context context, SupportMenu supportMenu) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return new MenuWrapperICS(context, supportMenu);
}
throw new UnsupportedOperationException();
}
public static MenuItem wrapSupportMenuItem(Context context, SupportMenuItem supportMenuItem) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
return new MenuItemWrapperJB(context, supportMenuItem);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return new MenuItemWrapperICS(context, supportMenuItem);
}
throw new UnsupportedOperationException();
}
public static SubMenu wrapSupportSubMenu(Context context, SupportSubMenu supportSubMenu) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return new SubMenuWrapperICS(context, supportSubMenu);
}
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,177 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.v4.internal.view.SupportMenu;
import android.support.v4.internal.view.SupportMenuItem;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
/**
* Wraps a support {@link SupportMenu} as a framework {@link android.view.Menu}
* @hide
*/
class MenuWrapperICS extends BaseMenuWrapper<SupportMenu> implements Menu {
MenuWrapperICS(Context context, SupportMenu object) {
super(context, object);
}
@Override
public MenuItem add(CharSequence title) {
return getMenuItemWrapper(mWrappedObject.add(title));
}
@Override
public MenuItem add(int titleRes) {
return getMenuItemWrapper(mWrappedObject.add(titleRes));
}
@Override
public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
return getMenuItemWrapper(mWrappedObject.add(groupId, itemId, order, title));
}
@Override
public MenuItem add(int groupId, int itemId, int order, int titleRes) {
return getMenuItemWrapper(mWrappedObject.add(groupId, itemId, order, titleRes));
}
@Override
public SubMenu addSubMenu(CharSequence title) {
return getSubMenuWrapper(mWrappedObject.addSubMenu(title));
}
@Override
public SubMenu addSubMenu(int titleRes) {
return getSubMenuWrapper(mWrappedObject.addSubMenu(titleRes));
}
@Override
public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
return getSubMenuWrapper(mWrappedObject.addSubMenu(groupId, itemId, order, title));
}
@Override
public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
return getSubMenuWrapper(
mWrappedObject.addSubMenu(groupId, itemId, order, titleRes));
}
@Override
public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller,
Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
android.view.MenuItem[] items = null;
if (outSpecificItems != null) {
items = new android.view.MenuItem[outSpecificItems.length];
}
int result = mWrappedObject
.addIntentOptions(groupId, itemId, order, caller, specifics, intent, flags, items);
if (items != null) {
for (int i = 0, z = items.length; i < z; i++) {
outSpecificItems[i] = getMenuItemWrapper(items[i]);
}
}
return result;
}
@Override
public void removeItem(int id) {
internalRemoveItem(id);
mWrappedObject.removeItem(id);
}
@Override
public void removeGroup(int groupId) {
internalRemoveGroup(groupId);
mWrappedObject.removeGroup(groupId);
}
@Override
public void clear() {
internalClear();
mWrappedObject.clear();
}
@Override
public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
mWrappedObject.setGroupCheckable(group, checkable, exclusive);
}
@Override
public void setGroupVisible(int group, boolean visible) {
mWrappedObject.setGroupVisible(group, visible);
}
@Override
public void setGroupEnabled(int group, boolean enabled) {
mWrappedObject.setGroupEnabled(group, enabled);
}
@Override
public boolean hasVisibleItems() {
return mWrappedObject.hasVisibleItems();
}
@Override
public MenuItem findItem(int id) {
return getMenuItemWrapper(mWrappedObject.findItem(id));
}
@Override
public int size() {
return mWrappedObject.size();
}
@Override
public MenuItem getItem(int index) {
return getMenuItemWrapper(mWrappedObject.getItem(index));
}
@Override
public void close() {
mWrappedObject.close();
}
@Override
public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
return mWrappedObject.performShortcut(keyCode, event, flags);
}
@Override
public boolean isShortcutKey(int keyCode, KeyEvent event) {
return mWrappedObject.isShortcutKey(keyCode, event);
}
@Override
public boolean performIdentifierAction(int id, int flags) {
return mWrappedObject.performIdentifierAction(id, flags);
}
@Override
public void setQwertyMode(boolean isQwerty) {
mWrappedObject.setQwertyMode(isQwerty);
}
}

View file

@ -0,0 +1,141 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
/**
* The model for a sub menu, which is an extension of the menu. Most methods are proxied to the
* parent menu.
*
* @hide
*/
public class SubMenuBuilder extends MenuBuilder implements SubMenu {
private MenuBuilder mParentMenu;
private MenuItemImpl mItem;
public SubMenuBuilder(Context context, MenuBuilder parentMenu, MenuItemImpl item) {
super(context);
mParentMenu = parentMenu;
mItem = item;
}
@Override
public void setQwertyMode(boolean isQwerty) {
mParentMenu.setQwertyMode(isQwerty);
}
@Override
public boolean isQwertyMode() {
return mParentMenu.isQwertyMode();
}
@Override
public void setShortcutsVisible(boolean shortcutsVisible) {
mParentMenu.setShortcutsVisible(shortcutsVisible);
}
@Override
public boolean isShortcutsVisible() {
return mParentMenu.isShortcutsVisible();
}
public Menu getParentMenu() {
return mParentMenu;
}
public MenuItem getItem() {
return mItem;
}
@Override
public void setCallback(Callback callback) {
mParentMenu.setCallback(callback);
}
@Override
public MenuBuilder getRootMenu() {
return mParentMenu;
}
@Override
boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
return super.dispatchMenuItemSelected(menu, item) ||
mParentMenu.dispatchMenuItemSelected(menu, item);
}
public SubMenu setIcon(Drawable icon) {
mItem.setIcon(icon);
return this;
}
public SubMenu setIcon(int iconRes) {
mItem.setIcon(iconRes);
return this;
}
public SubMenu setHeaderIcon(Drawable icon) {
super.setHeaderIconInt(icon);
return this;
}
public SubMenu setHeaderIcon(int iconRes) {
super.setHeaderIconInt(ContextCompat.getDrawable(getContext(), iconRes));
return this;
}
public SubMenu setHeaderTitle(CharSequence title) {
super.setHeaderTitleInt(title);
return this;
}
public SubMenu setHeaderTitle(int titleRes) {
super.setHeaderTitleInt(getContext().getResources().getString(titleRes));
return this;
}
public SubMenu setHeaderView(View view) {
super.setHeaderViewInt(view);
return this;
}
@Override
public boolean expandItemActionView(MenuItemImpl item) {
return mParentMenu.expandItemActionView(item);
}
@Override
public boolean collapseItemActionView(MenuItemImpl item) {
return mParentMenu.collapseItemActionView(item);
}
@Override
public String getActionViewStatesKey() {
final int itemId = mItem != null ? mItem.getItemId() : 0;
if (itemId == 0) {
return null;
}
return super.getActionViewStatesKey() + ":" + itemId;
}
}

View file

@ -0,0 +1,92 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 android.support.v7.internal.view.menu;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.internal.view.SupportSubMenu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
/**
* Wraps a support {@link SupportSubMenu} as a framework {@link android.view.SubMenu}
* @hide
*/
class SubMenuWrapperICS extends MenuWrapperICS implements SubMenu {
SubMenuWrapperICS(Context context, SupportSubMenu subMenu) {
super(context, subMenu);
}
@Override
public SupportSubMenu getWrappedObject() {
return (SupportSubMenu) mWrappedObject;
}
@Override
public SubMenu setHeaderTitle(int titleRes) {
getWrappedObject().setHeaderTitle(titleRes);
return this;
}
@Override
public SubMenu setHeaderTitle(CharSequence title) {
getWrappedObject().setHeaderTitle(title);
return this;
}
@Override
public SubMenu setHeaderIcon(int iconRes) {
getWrappedObject().setHeaderIcon(iconRes);
return this;
}
@Override
public SubMenu setHeaderIcon(Drawable icon) {
getWrappedObject().setHeaderIcon(icon);
return this;
}
@Override
public SubMenu setHeaderView(View view) {
getWrappedObject().setHeaderView(view);
return this;
}
@Override
public void clearHeader() {
getWrappedObject().clearHeader();
}
@Override
public SubMenu setIcon(int iconRes) {
getWrappedObject().setIcon(iconRes);
return this;
}
@Override
public SubMenu setIcon(Drawable icon) {
getWrappedObject().setIcon(icon);
return this;
}
@Override
public MenuItem getItem() {
return getMenuItemWrapper(getWrappedObject().getItem());
}
}

View file

@ -0,0 +1,288 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v7.appcompat.R;
import android.support.v7.internal.view.ViewPropertyAnimatorCompatSet;
import android.support.v7.widget.ActionMenuPresenter;
import android.support.v7.widget.ActionMenuView;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
abstract class AbsActionBarView extends ViewGroup {
private static final Interpolator sAlphaInterpolator = new DecelerateInterpolator();
private static final int FADE_DURATION = 200;
protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
/** Context against which to inflate popup menus. */
protected final Context mPopupContext;
protected ActionMenuView mMenuView;
protected ActionMenuPresenter mActionMenuPresenter;
protected ViewGroup mSplitView;
protected boolean mSplitActionBar;
protected boolean mSplitWhenNarrow;
protected int mContentHeight;
protected ViewPropertyAnimatorCompat mVisibilityAnim;
AbsActionBarView(Context context) {
this(context, null);
}
AbsActionBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
AbsActionBarView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final TypedValue tv = new TypedValue();
if (context.getTheme().resolveAttribute(R.attr.actionBarPopupTheme, tv, true)
&& tv.resourceId != 0) {
mPopupContext = new ContextThemeWrapper(context, tv.resourceId);
} else {
mPopupContext = context;
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
if (Build.VERSION.SDK_INT >= 8) {
super.onConfigurationChanged(newConfig);
}
// Action bar can change size on configuration changes.
// Reread the desired height from the theme-specified style.
TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.ActionBar,
R.attr.actionBarStyle, 0);
setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0));
a.recycle();
if (mActionMenuPresenter != null) {
mActionMenuPresenter.onConfigurationChanged(newConfig);
}
}
/**
* Sets whether the bar should be split right now, no questions asked.
* @param split true if the bar should split
*/
public void setSplitToolbar(boolean split) {
mSplitActionBar = split;
}
/**
* Sets whether the bar should split if we enter a narrow screen configuration.
* @param splitWhenNarrow true if the bar should check to split after a config change
*/
public void setSplitWhenNarrow(boolean splitWhenNarrow) {
mSplitWhenNarrow = splitWhenNarrow;
}
public void setContentHeight(int height) {
mContentHeight = height;
requestLayout();
}
public int getContentHeight() {
return mContentHeight;
}
public void setSplitView(ViewGroup splitView) {
mSplitView = splitView;
}
/**
* @return Current visibility or if animating, the visibility being animated to.
*/
public int getAnimatedVisibility() {
if (mVisibilityAnim != null) {
return mVisAnimListener.mFinalVisibility;
}
return getVisibility();
}
public void animateToVisibility(int visibility) {
if (mVisibilityAnim != null) {
mVisibilityAnim.cancel();
}
if (visibility == VISIBLE) {
if (getVisibility() != VISIBLE) {
ViewCompat.setAlpha(this, 0f);
if (mSplitView != null && mMenuView != null) {
ViewCompat.setAlpha(mMenuView, 0f);
}
}
ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(1f);
anim.setDuration(FADE_DURATION);
anim.setInterpolator(sAlphaInterpolator);
if (mSplitView != null && mMenuView != null) {
ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet();
ViewPropertyAnimatorCompat splitAnim = ViewCompat.animate(mMenuView).alpha(1f);
splitAnim.setDuration(FADE_DURATION);
set.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
set.play(anim).play(splitAnim);
set.start();
} else {
anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
anim.start();
}
} else {
ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(0f);
anim.setDuration(FADE_DURATION);
anim.setInterpolator(sAlphaInterpolator);
if (mSplitView != null && mMenuView != null) {
ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet();
ViewPropertyAnimatorCompat splitAnim = ViewCompat.animate(mMenuView).alpha(0f);
splitAnim.setDuration(FADE_DURATION);
set.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
set.play(anim).play(splitAnim);
set.start();
} else {
anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
anim.start();
}
}
}
public boolean showOverflowMenu() {
if (mActionMenuPresenter != null) {
return mActionMenuPresenter.showOverflowMenu();
}
return false;
}
public void postShowOverflowMenu() {
post(new Runnable() {
public void run() {
showOverflowMenu();
}
});
}
public boolean hideOverflowMenu() {
if (mActionMenuPresenter != null) {
return mActionMenuPresenter.hideOverflowMenu();
}
return false;
}
public boolean isOverflowMenuShowing() {
if (mActionMenuPresenter != null) {
return mActionMenuPresenter.isOverflowMenuShowing();
}
return false;
}
public boolean isOverflowMenuShowPending() {
if (mActionMenuPresenter != null) {
return mActionMenuPresenter.isOverflowMenuShowPending();
}
return false;
}
public boolean isOverflowReserved() {
return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
}
public boolean canShowOverflowMenu() {
return isOverflowReserved() && getVisibility() == VISIBLE;
}
public void dismissPopupMenus() {
if (mActionMenuPresenter != null) {
mActionMenuPresenter.dismissPopupMenus();
}
}
protected int measureChildView(View child, int availableWidth, int childSpecHeight,
int spacing) {
child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
childSpecHeight);
availableWidth -= child.getMeasuredWidth();
availableWidth -= spacing;
return Math.max(0, availableWidth);
}
static protected int next(int x, int val, boolean isRtl) {
return isRtl ? x - val : x + val;
}
protected int positionChild(View child, int x, int y, int contentHeight, boolean reverse) {
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int childTop = y + (contentHeight - childHeight) / 2;
if (reverse) {
child.layout(x - childWidth, childTop, x, childTop + childHeight);
} else {
child.layout(x, childTop, x + childWidth, childTop + childHeight);
}
return (reverse ? -childWidth : childWidth);
}
protected class VisibilityAnimListener implements ViewPropertyAnimatorListener {
private boolean mCanceled = false;
int mFinalVisibility;
public VisibilityAnimListener withFinalVisibility(ViewPropertyAnimatorCompat animation,
int visibility) {
mVisibilityAnim = animation;
mFinalVisibility = visibility;
return this;
}
@Override
public void onAnimationStart(View view) {
setVisibility(VISIBLE);
mCanceled = false;
}
@Override
public void onAnimationEnd(View view) {
if (mCanceled) return;
mVisibilityAnim = null;
setVisibility(mFinalVisibility);
if (mSplitView != null && mMenuView != null) {
mMenuView.setVisibility(mFinalVisibility);
}
}
@Override
public void onAnimationCancel(View view) {
mCanceled = true;
}
}
}

View file

@ -0,0 +1,451 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SpinnerAdapter;
/**
* An abstract base class for spinner widgets. SDK users will probably not
* need to use this class.
*/
abstract class AbsSpinnerCompat extends AdapterViewCompat<SpinnerAdapter> {
SpinnerAdapter mAdapter;
int mHeightMeasureSpec;
int mWidthMeasureSpec;
int mSelectionLeftPadding = 0;
int mSelectionTopPadding = 0;
int mSelectionRightPadding = 0;
int mSelectionBottomPadding = 0;
final Rect mSpinnerPadding = new Rect();
final RecycleBin mRecycler = new RecycleBin();
private DataSetObserver mDataSetObserver;
/** Temporary frame to hold a child View's frame rectangle */
private Rect mTouchFrame;
AbsSpinnerCompat(Context context) {
super(context);
initAbsSpinner();
}
AbsSpinnerCompat(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
AbsSpinnerCompat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initAbsSpinner();
}
/**
* Common code for different constructor flavors
*/
private void initAbsSpinner() {
setFocusable(true);
setWillNotDraw(false);
}
/**
* The Adapter is used to provide the data which backs this Spinner.
* It also provides methods to transform spinner items based on their position
* relative to the selected item.
* @param adapter The SpinnerAdapter to use for this Spinner
*/
@Override
public void setAdapter(SpinnerAdapter adapter) {
if (null != mAdapter) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
resetList();
}
mAdapter = adapter;
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
if (mAdapter != null) {
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
int position = mItemCount > 0 ? 0 : INVALID_POSITION;
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
checkFocus();
resetList();
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}
/**
* Clear out all children from the list
*/
void resetList() {
mDataChanged = false;
mNeedSync = false;
removeAllViewsInLayout();
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
setSelectedPositionInt(INVALID_POSITION);
setNextSelectedPositionInt(INVALID_POSITION);
invalidate();
}
/**
* @see android.view.View#measure(int, int)
*
* Figure out the dimensions of this Spinner. The width comes from
* the widthMeasureSpec as Spinnners can't have their width set to
* UNSPECIFIED. The height is based on the height of the selected item
* plus padding.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize;
int heightSize;
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
final int paddingRight = getPaddingRight();
final int paddingBottom = getPaddingBottom();
mSpinnerPadding.left = paddingLeft > mSelectionLeftPadding ? paddingLeft
: mSelectionLeftPadding;
mSpinnerPadding.top = paddingTop > mSelectionTopPadding ? paddingTop
: mSelectionTopPadding;
mSpinnerPadding.right = paddingRight > mSelectionRightPadding ? paddingRight
: mSelectionRightPadding;
mSpinnerPadding.bottom = paddingBottom > mSelectionBottomPadding ? paddingBottom
: mSelectionBottomPadding;
if (mDataChanged) {
handleDataChanged();
}
int preferredHeight = 0;
int preferredWidth = 0;
boolean needsMeasuring = true;
int selectedPosition = getSelectedItemPosition();
if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
// Try looking in the recycler. (Maybe we were measured once already)
View view = mRecycler.get(selectedPosition);
if (view == null) {
// Make a new one
view = mAdapter.getView(selectedPosition, null, this);
}
if (view != null) {
// Put in recycler for re-measuring and/or layout
mRecycler.put(selectedPosition, view);
if (view.getLayoutParams() == null) {
mBlockLayoutRequests = true;
view.setLayoutParams(generateDefaultLayoutParams());
mBlockLayoutRequests = false;
}
measureChild(view, widthMeasureSpec, heightMeasureSpec);
preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
needsMeasuring = false;
}
}
if (needsMeasuring) {
// No views -- just use padding
preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
if (widthMode == MeasureSpec.UNSPECIFIED) {
preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
}
}
preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
heightSize = ViewCompat.resolveSizeAndState(preferredHeight, heightMeasureSpec, 0);
widthSize = ViewCompat.resolveSizeAndState(preferredWidth, widthMeasureSpec, 0);
setMeasuredDimension(widthSize, heightSize);
mHeightMeasureSpec = heightMeasureSpec;
mWidthMeasureSpec = widthMeasureSpec;
}
int getChildHeight(View child) {
return child.getMeasuredHeight();
}
int getChildWidth(View child) {
return child.getMeasuredWidth();
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
void recycleAllViews() {
final int childCount = getChildCount();
final AbsSpinnerCompat.RecycleBin recycleBin = mRecycler;
final int position = mFirstPosition;
// All views go in recycler
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
int index = position + i;
recycleBin.put(index, v);
}
}
/**
* Jump directly to a specific item in the adapter data.
*/
public void setSelection(int position, boolean animate) {
// Animate only if requested position is already on screen somewhere
boolean shouldAnimate = animate && mFirstPosition <= position &&
position <= mFirstPosition + getChildCount() - 1;
setSelectionInt(position, shouldAnimate);
}
@Override
public void setSelection(int position) {
setNextSelectedPositionInt(position);
requestLayout();
invalidate();
}
/**
* Makes the item at the supplied position selected.
*
* @param position Position to select
* @param animate Should the transition be animated
*
*/
void setSelectionInt(int position, boolean animate) {
if (position != mOldSelectedPosition) {
mBlockLayoutRequests = true;
int delta = position - mSelectedPosition;
setNextSelectedPositionInt(position);
layout(delta, animate);
mBlockLayoutRequests = false;
}
}
abstract void layout(int delta, boolean animate);
@Override
public View getSelectedView() {
if (mItemCount > 0 && mSelectedPosition >= 0) {
return getChildAt(mSelectedPosition - mFirstPosition);
} else {
return null;
}
}
/**
* Override to prevent spamming ourselves with layout requests
* as we place views
*
* @see android.view.View#requestLayout()
*/
@Override
public void requestLayout() {
if (!mBlockLayoutRequests) {
super.requestLayout();
}
}
@Override
public SpinnerAdapter getAdapter() {
return mAdapter;
}
@Override
public int getCount() {
return mItemCount;
}
/**
* Maps a point to a position in the list.
*
* @param x X in local coordinate
* @param y Y in local coordinate
* @return The position of the item which contains the specified point, or
* {@link #INVALID_POSITION} if the point does not intersect an item.
*/
public int pointToPosition(int x, int y) {
Rect frame = mTouchFrame;
if (frame == null) {
mTouchFrame = new Rect();
frame = mTouchFrame;
}
final int count = getChildCount();
for (int i = count - 1; i >= 0; i--) {
View child = getChildAt(i);
if (child.getVisibility() == View.VISIBLE) {
child.getHitRect(frame);
if (frame.contains(x, y)) {
return mFirstPosition + i;
}
}
}
return INVALID_POSITION;
}
static class SavedState extends BaseSavedState {
long selectedId;
int position;
/**
* Constructor called from {@link AbsSpinnerCompat#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
SavedState(Parcel in) {
super(in);
selectedId = in.readLong();
position = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeLong(selectedId);
out.writeInt(position);
}
@Override
public String toString() {
return "AbsSpinner.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " selectedId=" + selectedId
+ " position=" + position + "}";
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.selectedId = getSelectedItemId();
if (ss.selectedId >= 0) {
ss.position = getSelectedItemPosition();
} else {
ss.position = INVALID_POSITION;
}
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.selectedId >= 0) {
mDataChanged = true;
mNeedSync = true;
mSyncRowId = ss.selectedId;
mSyncPosition = ss.position;
mSyncMode = SYNC_SELECTED_POSITION;
requestLayout();
}
}
class RecycleBin {
private final SparseArray<View> mScrapHeap = new SparseArray<View>();
public void put(int position, View v) {
mScrapHeap.put(position, v);
}
View get(int position) {
// System.out.print("Looking for " + position);
View result = mScrapHeap.get(position);
if (result != null) {
// System.out.println(" HIT");
mScrapHeap.delete(position);
} else {
// System.out.println(" MISS");
}
return result;
}
void clear() {
final SparseArray<View> scrapHeap = mScrapHeap;
final int count = scrapHeap.size();
for (int i = 0; i < count; i++) {
final View view = scrapHeap.valueAt(i);
if (view != null) {
removeDetachedView(view, true);
}
}
scrapHeap.clear();
}
}
}

View file

@ -0,0 +1,44 @@
package android.support.v7.internal.widget;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
class ActionBarBackgroundDrawable extends Drawable {
final ActionBarContainer mContainer;
public ActionBarBackgroundDrawable(ActionBarContainer container) {
mContainer = container;
}
@Override
public void draw(Canvas canvas) {
if (mContainer.mIsSplit) {
if (mContainer.mSplitBackground != null) {
mContainer.mSplitBackground.draw(canvas);
}
} else {
if (mContainer.mBackground != null) {
mContainer.mBackground.draw(canvas);
}
if (mContainer.mStackedBackground != null && mContainer.mIsStacked) {
mContainer.mStackedBackground.draw(canvas);
}
}
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter cf) {
}
@Override
public int getOpacity() {
return 0;
}
}

View file

@ -0,0 +1,26 @@
package android.support.v7.internal.widget;
import android.graphics.Outline;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
class ActionBarBackgroundDrawableV21 extends ActionBarBackgroundDrawable {
public ActionBarBackgroundDrawableV21(ActionBarContainer container) {
super(container);
}
@Override
public void getOutline(@NonNull Outline outline) {
if (mContainer.mIsSplit) {
if (mContainer.mSplitBackground != null) {
mContainer.mSplitBackground.getOutline(outline);
}
} else {
// ignore the stacked background for shadow casting
if (mContainer.mBackground != null) {
mContainer.mBackground.getOutline(outline);
}
}
}
}

View file

@ -0,0 +1,326 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v7.appcompat.R;
import android.support.v7.internal.VersionUtils;
import android.support.v7.view.ActionMode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
/**
* This class acts as a container for the action bar view and action mode context views.
* It applies special styles as needed to help handle animated transitions between them.
* @hide
*/
public class ActionBarContainer extends FrameLayout {
private boolean mIsTransitioning;
private View mTabContainer;
private View mActionBarView;
private View mContextView;
Drawable mBackground;
Drawable mStackedBackground;
Drawable mSplitBackground;
boolean mIsSplit;
boolean mIsStacked;
private int mHeight;
public ActionBarContainer(Context context) {
this(context, null);
}
public ActionBarContainer(Context context, AttributeSet attrs) {
super(context, attrs);
// Set a transparent background so that we project appropriately.
final Drawable bg = VersionUtils.isAtLeastL()
? new ActionBarBackgroundDrawableV21(this)
: new ActionBarBackgroundDrawable(this);
setBackgroundDrawable(bg);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ActionBar);
mBackground = a.getDrawable(R.styleable.ActionBar_background);
mStackedBackground = a.getDrawable(
R.styleable.ActionBar_backgroundStacked);
mHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, -1);
if (getId() == R.id.split_action_bar) {
mIsSplit = true;
mSplitBackground = a.getDrawable(R.styleable.ActionBar_backgroundSplit);
}
a.recycle();
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
mActionBarView = findViewById(R.id.action_bar);
mContextView = findViewById(R.id.action_context_bar);
}
public void setPrimaryBackground(Drawable bg) {
if (mBackground != null) {
mBackground.setCallback(null);
unscheduleDrawable(mBackground);
}
mBackground = bg;
if (bg != null) {
bg.setCallback(this);
if (mActionBarView != null) {
mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
mActionBarView.getRight(), mActionBarView.getBottom());
}
}
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
invalidate();
}
public void setStackedBackground(Drawable bg) {
if (mStackedBackground != null) {
mStackedBackground.setCallback(null);
unscheduleDrawable(mStackedBackground);
}
mStackedBackground = bg;
if (bg != null) {
bg.setCallback(this);
if ((mIsStacked && mStackedBackground != null)) {
mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
mTabContainer.getRight(), mTabContainer.getBottom());
}
}
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
invalidate();
}
public void setSplitBackground(Drawable bg) {
if (mSplitBackground != null) {
mSplitBackground.setCallback(null);
unscheduleDrawable(mSplitBackground);
}
mSplitBackground = bg;
if (bg != null) {
bg.setCallback(this);
if (mIsSplit && mSplitBackground != null) {
mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
}
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
invalidate();
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
final boolean isVisible = visibility == VISIBLE;
if (mBackground != null) mBackground.setVisible(isVisible, false);
if (mStackedBackground != null) mStackedBackground.setVisible(isVisible, false);
if (mSplitBackground != null) mSplitBackground.setVisible(isVisible, false);
}
@Override
protected boolean verifyDrawable(Drawable who) {
return (who == mBackground && !mIsSplit) || (who == mStackedBackground && mIsStacked) ||
(who == mSplitBackground && mIsSplit) || super.verifyDrawable(who);
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mBackground != null && mBackground.isStateful()) {
mBackground.setState(getDrawableState());
}
if (mStackedBackground != null && mStackedBackground.isStateful()) {
mStackedBackground.setState(getDrawableState());
}
if (mSplitBackground != null && mSplitBackground.isStateful()) {
mSplitBackground.setState(getDrawableState());
}
}
public void jumpDrawablesToCurrentState() {
if (Build.VERSION.SDK_INT >= 11) {
super.jumpDrawablesToCurrentState();
if (mBackground != null) {
mBackground.jumpToCurrentState();
}
if (mStackedBackground != null) {
mStackedBackground.jumpToCurrentState();
}
if (mSplitBackground != null) {
mSplitBackground.jumpToCurrentState();
}
}
}
/**
* Set the action bar into a "transitioning" state. While transitioning the bar will block focus
* and touch from all of its descendants. This prevents the user from interacting with the bar
* while it is animating in or out.
*
* @param isTransitioning true if the bar is currently transitioning, false otherwise.
*/
public void setTransitioning(boolean isTransitioning) {
mIsTransitioning = isTransitioning;
setDescendantFocusability(isTransitioning ? FOCUS_BLOCK_DESCENDANTS
: FOCUS_AFTER_DESCENDANTS);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mIsTransitioning || super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
// An action bar always eats touch events.
return true;
}
public void setTabContainer(ScrollingTabContainerView tabView) {
if (mTabContainer != null) {
removeView(mTabContainer);
}
mTabContainer = tabView;
if (tabView != null) {
addView(tabView);
final ViewGroup.LayoutParams lp = tabView.getLayoutParams();
lp.width = LayoutParams.MATCH_PARENT;
lp.height = LayoutParams.WRAP_CONTENT;
tabView.setAllowCollapse(false);
}
}
public View getTabContainer() {
return mTabContainer;
}
//@Override
public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
// No starting an action mode for an action bar child! (Where would it go?)
return null;
}
@Override
public android.view.ActionMode startActionModeForChild(View originalView,
android.view.ActionMode.Callback callback) {
return null;
}
private boolean isCollapsed(View view) {
return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0;
}
private int getMeasuredHeightWithMargins(View view) {
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
return view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mActionBarView == null &&
MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mActionBarView == null) return;
final int mode = MeasureSpec.getMode(heightMeasureSpec);
if (mTabContainer != null && mTabContainer.getVisibility() != GONE
&& mode != MeasureSpec.EXACTLY) {
final int topMarginForTabs;
if (!isCollapsed(mActionBarView)) {
topMarginForTabs = getMeasuredHeightWithMargins(mActionBarView);
} else if (!isCollapsed(mContextView)) {
topMarginForTabs = getMeasuredHeightWithMargins(mContextView);
} else {
topMarginForTabs = 0;
}
final int maxHeight = mode == MeasureSpec.AT_MOST ?
MeasureSpec.getSize(heightMeasureSpec) : Integer.MAX_VALUE;
setMeasuredDimension(getMeasuredWidth(),
Math.min(topMarginForTabs + getMeasuredHeightWithMargins(mTabContainer),
maxHeight));
}
}
@Override
public void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
final View tabContainer = mTabContainer;
final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE;
if (tabContainer != null && tabContainer.getVisibility() != GONE) {
final int containerHeight = getMeasuredHeight();
final LayoutParams lp = (LayoutParams) tabContainer.getLayoutParams();
final int tabHeight = tabContainer.getMeasuredHeight();
tabContainer.layout(l, containerHeight - tabHeight - lp.bottomMargin, r,
containerHeight - lp.bottomMargin);
}
boolean needsInvalidate = false;
if (mIsSplit) {
if (mSplitBackground != null) {
mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
needsInvalidate = true;
}
} else {
if (mBackground != null) {
if (mActionBarView.getVisibility() == View.VISIBLE) {
mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
mActionBarView.getRight(), mActionBarView.getBottom());
} else if (mContextView != null &&
mContextView.getVisibility() == View.VISIBLE) {
mBackground.setBounds(mContextView.getLeft(), mContextView.getTop(),
mContextView.getRight(), mContextView.getBottom());
} else {
mBackground.setBounds(0, 0, 0, 0);
}
needsInvalidate = true;
}
mIsStacked = hasTabs;
if (hasTabs && mStackedBackground != null) {
mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(),
tabContainer.getRight(), tabContainer.getBottom());
needsInvalidate = true;
}
}
if (needsInvalidate) {
invalidate();
}
}
}

View file

@ -0,0 +1,546 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v7.appcompat.R;
import android.support.v7.internal.view.ViewPropertyAnimatorCompatSet;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.ActionMenuPresenter;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* @hide
*/
public class ActionBarContextView extends AbsActionBarView implements ViewPropertyAnimatorListener {
private static final String TAG = "ActionBarContextView";
private CharSequence mTitle;
private CharSequence mSubtitle;
private View mClose;
private View mCustomView;
private LinearLayout mTitleLayout;
private TextView mTitleView;
private TextView mSubtitleView;
private int mTitleStyleRes;
private int mSubtitleStyleRes;
private Drawable mSplitBackground;
private boolean mTitleOptional;
private int mCloseItemLayout;
private ViewPropertyAnimatorCompatSet mCurrentAnimation;
private boolean mAnimateInOnLayout;
private int mAnimationMode;
private static final int ANIMATE_IDLE = 0;
private static final int ANIMATE_IN = 1;
private static final int ANIMATE_OUT = 2;
public ActionBarContextView(Context context) {
this(context, null);
}
public ActionBarContextView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.actionModeStyle);
}
public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
R.styleable.ActionMode, defStyle, 0);
setBackgroundDrawable(a.getDrawable(
R.styleable.ActionMode_background));
mTitleStyleRes = a.getResourceId(
R.styleable.ActionMode_titleTextStyle, 0);
mSubtitleStyleRes = a.getResourceId(
R.styleable.ActionMode_subtitleTextStyle, 0);
mContentHeight = a.getLayoutDimension(
R.styleable.ActionMode_height, 0);
mSplitBackground = a.getDrawable(
R.styleable.ActionMode_backgroundSplit);
mCloseItemLayout = a.getResourceId(
R.styleable.ActionMode_closeItemLayout,
R.layout.abc_action_mode_close_item_material);
a.recycle();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mActionMenuPresenter != null) {
mActionMenuPresenter.hideOverflowMenu();
mActionMenuPresenter.hideSubMenus();
}
}
@Override
public void setSplitToolbar(boolean split) {
if (mSplitActionBar != split) {
if (mActionMenuPresenter != null) {
// Mode is already active; move everything over and adjust the menu itself.
final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
if (!split) {
mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
mMenuView.setBackgroundDrawable(null);
final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
if (oldParent != null) oldParent.removeView(mMenuView);
addView(mMenuView, layoutParams);
} else {
// Allow full screen width in split mode.
mActionMenuPresenter.setWidthLimit(
getContext().getResources().getDisplayMetrics().widthPixels, true);
// No limit to the item count; use whatever will fit.
mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
// Span the whole width
layoutParams.width = LayoutParams.MATCH_PARENT;
layoutParams.height = mContentHeight;
mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
mMenuView.setBackgroundDrawable(mSplitBackground);
final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
if (oldParent != null) oldParent.removeView(mMenuView);
mSplitView.addView(mMenuView, layoutParams);
}
}
super.setSplitToolbar(split);
}
}
public void setContentHeight(int height) {
mContentHeight = height;
}
public void setCustomView(View view) {
if (mCustomView != null) {
removeView(mCustomView);
}
mCustomView = view;
if (mTitleLayout != null) {
removeView(mTitleLayout);
mTitleLayout = null;
}
if (view != null) {
addView(view);
}
requestLayout();
}
public void setTitle(CharSequence title) {
mTitle = title;
initTitle();
}
public void setSubtitle(CharSequence subtitle) {
mSubtitle = subtitle;
initTitle();
}
public CharSequence getTitle() {
return mTitle;
}
public CharSequence getSubtitle() {
return mSubtitle;
}
private void initTitle() {
if (mTitleLayout == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.abc_action_bar_title_item, this);
mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1);
mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
if (mTitleStyleRes != 0) {
mTitleView.setTextAppearance(getContext(), mTitleStyleRes);
}
if (mSubtitleStyleRes != 0) {
mSubtitleView.setTextAppearance(getContext(), mSubtitleStyleRes);
}
}
mTitleView.setText(mTitle);
mSubtitleView.setText(mSubtitle);
final boolean hasTitle = !TextUtils.isEmpty(mTitle);
final boolean hasSubtitle = !TextUtils.isEmpty(mSubtitle);
mSubtitleView.setVisibility(hasSubtitle ? VISIBLE : GONE);
mTitleLayout.setVisibility(hasTitle || hasSubtitle ? VISIBLE : GONE);
if (mTitleLayout.getParent() == null) {
addView(mTitleLayout);
}
}
public void initForMode(final ActionMode mode) {
if (mClose == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
mClose = inflater.inflate(mCloseItemLayout, this, false);
addView(mClose);
} else if (mClose.getParent() == null) {
addView(mClose);
}
View closeButton = mClose.findViewById(R.id.action_mode_close_button);
closeButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mode.finish();
}
});
final MenuBuilder menu = (MenuBuilder) mode.getMenu();
if (mActionMenuPresenter != null) {
mActionMenuPresenter.dismissPopupMenus();
}
mActionMenuPresenter = new ActionMenuPresenter(getContext());
mActionMenuPresenter.setReserveOverflow(true);
final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
if (!mSplitActionBar) {
menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
mMenuView.setBackgroundDrawable(null);
addView(mMenuView, layoutParams);
} else {
// Allow full screen width in split mode.
mActionMenuPresenter.setWidthLimit(
getContext().getResources().getDisplayMetrics().widthPixels, true);
// No limit to the item count; use whatever will fit.
mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
// Span the whole width
layoutParams.width = LayoutParams.MATCH_PARENT;
layoutParams.height = mContentHeight;
menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
mMenuView.setBackgroundDrawable(mSplitBackground);
mSplitView.addView(mMenuView, layoutParams);
}
mAnimateInOnLayout = true;
}
public void closeMode() {
if (mAnimationMode == ANIMATE_OUT) {
// Called again during close; just finish what we were doing.
return;
}
if (mClose == null) {
killMode();
return;
}
finishAnimation();
mAnimationMode = ANIMATE_OUT;
mCurrentAnimation = makeOutAnimation();
mCurrentAnimation.start();
}
private void finishAnimation() {
final ViewPropertyAnimatorCompatSet a = mCurrentAnimation;
if (a != null) {
mCurrentAnimation = null;
a.cancel();
}
}
public void killMode() {
finishAnimation();
removeAllViews();
if (mSplitView != null) {
mSplitView.removeView(mMenuView);
}
mCustomView = null;
mMenuView = null;
mAnimateInOnLayout = false;
}
@Override
public boolean showOverflowMenu() {
if (mActionMenuPresenter != null) {
return mActionMenuPresenter.showOverflowMenu();
}
return false;
}
@Override
public boolean hideOverflowMenu() {
if (mActionMenuPresenter != null) {
return mActionMenuPresenter.hideOverflowMenu();
}
return false;
}
@Override
public boolean isOverflowMenuShowing() {
if (mActionMenuPresenter != null) {
return mActionMenuPresenter.isOverflowMenuShowing();
}
return false;
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
// Used by custom views if they don't supply layout params. Everything else
// added to an ActionBarContextView should have them already.
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
"with android:layout_width=\"match_parent\" (or fill_parent)");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.UNSPECIFIED) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
"with android:layout_height=\"wrap_content\"");
}
final int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxHeight = mContentHeight > 0 ?
mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
final int verticalPadding = getPaddingTop() + getPaddingBottom();
int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
final int height = maxHeight - verticalPadding;
final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
if (mClose != null) {
availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0);
MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
availableWidth -= lp.leftMargin + lp.rightMargin;
}
if (mMenuView != null && mMenuView.getParent() == this) {
availableWidth = measureChildView(mMenuView, availableWidth,
childSpecHeight, 0);
}
if (mTitleLayout != null && mCustomView == null) {
if (mTitleOptional) {
final int titleWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
mTitleLayout.measure(titleWidthSpec, childSpecHeight);
final int titleWidth = mTitleLayout.getMeasuredWidth();
final boolean titleFits = titleWidth <= availableWidth;
if (titleFits) {
availableWidth -= titleWidth;
}
mTitleLayout.setVisibility(titleFits ? VISIBLE : GONE);
} else {
availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
}
}
if (mCustomView != null) {
ViewGroup.LayoutParams lp = mCustomView.getLayoutParams();
final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
final int customWidth = lp.width >= 0 ?
Math.min(lp.width, availableWidth) : availableWidth;
final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
final int customHeight = lp.height >= 0 ?
Math.min(lp.height, height) : height;
mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode),
MeasureSpec.makeMeasureSpec(customHeight, customHeightMode));
}
if (mContentHeight <= 0) {
int measuredHeight = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View v = getChildAt(i);
int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
if (paddedViewHeight > measuredHeight) {
measuredHeight = paddedViewHeight;
}
}
setMeasuredDimension(contentWidth, measuredHeight);
} else {
setMeasuredDimension(contentWidth, maxHeight);
}
}
private ViewPropertyAnimatorCompatSet makeInAnimation() {
ViewCompat.setTranslationX(mClose, -mClose.getWidth() -
((MarginLayoutParams) mClose.getLayoutParams()).leftMargin);
ViewPropertyAnimatorCompat buttonAnimator = ViewCompat.animate(mClose).translationX(0);
buttonAnimator.setDuration(200);
buttonAnimator.setListener(this);
buttonAnimator.setInterpolator(new DecelerateInterpolator());
ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet();
set.play(buttonAnimator);
if (mMenuView != null) {
final int count = mMenuView.getChildCount();
if (count > 0) {
for (int i = count - 1, j = 0; i >= 0; i--, j++) {
View child = mMenuView.getChildAt(i);
ViewCompat.setScaleY(child, 0);
ViewPropertyAnimatorCompat a = ViewCompat.animate(child).scaleY(1);
a.setDuration(300);
set.play(a);
}
}
}
return set;
}
private ViewPropertyAnimatorCompatSet makeOutAnimation() {
ViewPropertyAnimatorCompat buttonAnimator = ViewCompat.animate(mClose)
.translationX(-mClose.getWidth() -
((MarginLayoutParams) mClose.getLayoutParams()).leftMargin);
buttonAnimator.setDuration(200);
buttonAnimator.setListener(this);
buttonAnimator.setInterpolator(new DecelerateInterpolator());
ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet();
set.play(buttonAnimator);
if (mMenuView != null) {
final int count = mMenuView.getChildCount();
if (count > 0) {
for (int i = 0; i < 0; i++) {
View child = mMenuView.getChildAt(i);
ViewCompat.setScaleY(child, 1);
ViewPropertyAnimatorCompat a = ViewCompat.animate(child).scaleY(0);
a.setDuration(300);
set.play(a);
}
}
}
return set;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft();
final int y = getPaddingTop();
final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
if (mClose != null && mClose.getVisibility() != GONE) {
MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
final int startMargin = (isLayoutRtl ? lp.rightMargin : lp.leftMargin);
final int endMargin = (isLayoutRtl ? lp.leftMargin : lp.rightMargin);
x = next(x, startMargin, isLayoutRtl);
x += positionChild(mClose, x, y, contentHeight, isLayoutRtl);
x = next(x, endMargin, isLayoutRtl);
if (mAnimateInOnLayout) {
mAnimationMode = ANIMATE_IN;
mCurrentAnimation = makeInAnimation();
mCurrentAnimation.start();
mAnimateInOnLayout = false;
}
}
if (mTitleLayout != null && mCustomView == null && mTitleLayout.getVisibility() != GONE) {
x += positionChild(mTitleLayout, x, y, contentHeight, isLayoutRtl);
}
if (mCustomView != null) {
x += positionChild(mCustomView, x, y, contentHeight, isLayoutRtl);
}
x = isLayoutRtl ? getPaddingLeft() : r - l - getPaddingRight();
if (mMenuView != null) {
x += positionChild(mMenuView, x, y, contentHeight, !isLayoutRtl);
}
}
@Override
public void onAnimationStart(View view) {
}
@Override
public void onAnimationEnd(View view) {
if (mAnimationMode == ANIMATE_OUT) {
killMode();
}
mAnimationMode = ANIMATE_IDLE;
}
@Override
public void onAnimationCancel(View view) {
}
@Override
public boolean shouldDelayChildPressedState() {
return false;
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
if (Build.VERSION.SDK_INT >= 14) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
// Action mode started
event.setSource(this);
event.setClassName(getClass().getName());
event.setPackageName(getContext().getPackageName());
event.setContentDescription(mTitle);
} else {
super.onInitializeAccessibilityEvent(event);
}
}
}
public void setTitleOptional(boolean titleOptional) {
if (titleOptional != mTitleOptional) {
requestLayout();
}
mTitleOptional = titleOptional;
}
public boolean isTitleOptional() {
return mTitleOptional;
}
}

View file

@ -0,0 +1,822 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.support.v4.widget.ScrollerCompat;
import android.support.v7.appcompat.R;
import android.support.v7.internal.VersionUtils;
import android.support.v7.internal.view.menu.MenuPresenter;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
/**
* Special layout for the containing of an overlay action bar (and its content) to correctly handle
* fitting system windows when the content has request that its layout ignore them.
*
* @hide
*/
public class ActionBarOverlayLayout extends ViewGroup implements DecorContentParent {
private static final String TAG = "ActionBarOverlayLayout";
private int mActionBarHeight;
//private WindowDecorActionBar mActionBar;
private int mWindowVisibility = View.VISIBLE;
// The main UI elements that we handle the layout of.
private ContentFrameLayout mContent;
private ActionBarContainer mActionBarBottom;
private ActionBarContainer mActionBarTop;
// Some interior UI elements.
private DecorToolbar mDecorToolbar;
// Content overlay drawable - generally the action bar's shadow
private Drawable mWindowContentOverlay;
private boolean mIgnoreWindowContentOverlay;
private boolean mOverlayMode;
private boolean mHasNonEmbeddedTabs;
private boolean mHideOnContentScroll;
private boolean mAnimatingForFling;
private int mHideOnContentScrollReference;
private int mLastSystemUiVisibility;
private final Rect mBaseContentInsets = new Rect();
private final Rect mLastBaseContentInsets = new Rect();
private final Rect mContentInsets = new Rect();
private final Rect mBaseInnerInsets = new Rect();
private final Rect mInnerInsets = new Rect();
private final Rect mLastInnerInsets = new Rect();
private ActionBarVisibilityCallback mActionBarVisibilityCallback;
private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms
private ScrollerCompat mFlingEstimator;
private ViewPropertyAnimatorCompat mCurrentActionBarTopAnimator;
private ViewPropertyAnimatorCompat mCurrentActionBarBottomAnimator;
private final ViewPropertyAnimatorListener mTopAnimatorListener
= new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationEnd(View view) {
mCurrentActionBarTopAnimator = null;
mAnimatingForFling = false;
}
@Override
public void onAnimationCancel(View view) {
mCurrentActionBarTopAnimator = null;
mAnimatingForFling = false;
}
};
private final ViewPropertyAnimatorListener mBottomAnimatorListener =
new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationEnd(View view) {
mCurrentActionBarBottomAnimator = null;
mAnimatingForFling = false;
}
@Override
public void onAnimationCancel(View view) {
mCurrentActionBarBottomAnimator = null;
mAnimatingForFling = false;
}
};
private final Runnable mRemoveActionBarHideOffset = new Runnable() {
public void run() {
haltActionBarHideOffsetAnimations();
mCurrentActionBarTopAnimator = ViewCompat.animate(mActionBarTop).translationY(0)
.setListener(mTopAnimatorListener);
if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
mCurrentActionBarBottomAnimator = ViewCompat.animate(mActionBarBottom).translationY(0)
.setListener(mBottomAnimatorListener);
}
}
};
private final Runnable mAddActionBarHideOffset = new Runnable() {
public void run() {
haltActionBarHideOffsetAnimations();
mCurrentActionBarTopAnimator = ViewCompat.animate(mActionBarTop)
.translationY(-mActionBarTop.getHeight())
.setListener(mTopAnimatorListener);
if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
mCurrentActionBarBottomAnimator = ViewCompat.animate(mActionBarBottom)
.translationY(mActionBarBottom.getHeight())
.setListener(mBottomAnimatorListener);
}
}
};
// public static final Property<ActionBarOverlayLayout, Integer> ACTION_BAR_HIDE_OFFSET =
// new IntProperty<ActionBarOverlayLayout>("actionBarHideOffset") {
//
// @Override
// public void setValue(ActionBarOverlayLayout object, int value) {
// object.setActionBarHideOffset(value);
// }
//
// @Override
// public Integer get(ActionBarOverlayLayout object) {
// return object.getActionBarHideOffset();
// }
// };
static final int[] ATTRS = new int [] {
R.attr.actionBarSize,
android.R.attr.windowContentOverlay
};
public ActionBarOverlayLayout(Context context) {
super(context);
init(context);
}
public ActionBarOverlayLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS);
mActionBarHeight = ta.getDimensionPixelSize(0, 0);
mWindowContentOverlay = ta.getDrawable(1);
setWillNotDraw(mWindowContentOverlay == null);
ta.recycle();
mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT;
mFlingEstimator = ScrollerCompat.create(context);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
haltActionBarHideOffsetAnimations();
}
public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) {
mActionBarVisibilityCallback = cb;
if (getWindowToken() != null) {
// This is being initialized after being added to a window;
// make sure to update all state now.
mActionBarVisibilityCallback.onWindowVisibilityChanged(mWindowVisibility);
if (mLastSystemUiVisibility != 0) {
int newVis = mLastSystemUiVisibility;
onWindowSystemUiVisibilityChanged(newVis);
ViewCompat.requestApplyInsets(this);
}
}
}
public void setOverlayMode(boolean overlayMode) {
mOverlayMode = overlayMode;
/*
* Drawing the window content overlay was broken before K so starting to draw it
* again unexpectedly will cause artifacts in some apps. They should fix it.
*/
mIgnoreWindowContentOverlay = overlayMode &&
getContext().getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT;
}
public boolean isInOverlayMode() {
return mOverlayMode;
}
public void setHasNonEmbeddedTabs(boolean hasNonEmbeddedTabs) {
mHasNonEmbeddedTabs = hasNonEmbeddedTabs;
}
public void setShowingForActionMode(boolean showing) {
// TODO: Add workaround for this
// if (showing) {
// // Here's a fun hack: if the status bar is currently being hidden,
// // and the application has asked for stable content insets, then
// // we will end up with the action mode action bar being shown
// // without the status bar, but moved below where the status bar
// // would be. Not nice. Trying to have this be positioned
// // correctly is not easy (basically we need yet *another* content
// // inset from the window manager to know where to put it), so
// // instead we will just temporarily force the status bar to be shown.
// if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// | SYSTEM_UI_FLAG_LAYOUT_STABLE))
// == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) {
// setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
// }
// } else {
// setDisabledSystemUiVisibility(0);
// }
}
protected void onConfigurationChanged(Configuration newConfig) {
if (Build.VERSION.SDK_INT >= 8) {
super.onConfigurationChanged(newConfig);
}
init(getContext());
ViewCompat.requestApplyInsets(this);
}
public void onWindowSystemUiVisibilityChanged(int visible) {
if (Build.VERSION.SDK_INT >= 16) {
super.onWindowSystemUiVisibilityChanged(visible);
}
pullChildren();
final int diff = mLastSystemUiVisibility ^ visible;
mLastSystemUiVisibility = visible;
final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0;
final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
if (mActionBarVisibilityCallback != null) {
// We want the bar to be visible if it is not being hidden,
// or the app has not turned on a stable UI mode (meaning they
// are performing explicit layout around the action bar).
mActionBarVisibilityCallback.enableContentAnimations(!stable);
if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem();
else mActionBarVisibilityCallback.hideForSystem();
}
if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
if (mActionBarVisibilityCallback != null) {
ViewCompat.requestApplyInsets(this);
}
}
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
mWindowVisibility = visibility;
if (mActionBarVisibilityCallback != null) {
mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility);
}
}
private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
boolean bottom, boolean right) {
boolean changed = false;
LayoutParams lp = (LayoutParams)view.getLayoutParams();
if (left && lp.leftMargin != insets.left) {
changed = true;
lp.leftMargin = insets.left;
}
if (top && lp.topMargin != insets.top) {
changed = true;
lp.topMargin = insets.top;
}
if (right && lp.rightMargin != insets.right) {
changed = true;
lp.rightMargin = insets.right;
}
if (bottom && lp.bottomMargin != insets.bottom) {
changed = true;
lp.bottomMargin = insets.bottom;
}
return changed;
}
@Override
protected boolean fitSystemWindows(Rect insets) {
pullChildren();
final int vis = ViewCompat.getWindowSystemUiVisibility(this);
final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
final Rect systemInsets = insets;
// Since we're not the top level view in the window decor, we do not need to
// inset the Action Bars
boolean changed = false;
mBaseInnerInsets.set(systemInsets);
ViewUtils.computeFitSystemWindows(this, mBaseInnerInsets, mBaseContentInsets);
if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
changed = true;
mLastBaseContentInsets.set(mBaseContentInsets);
}
if (changed) {
requestLayout();
}
// We don't do any more at this point. To correctly compute the content/inner
// insets in all cases, we need to know the measured size of the various action
// bar elements. fitSystemWindows() happens before the measure pass, so we can't
// do that here. Instead we will take this up in onMeasure().
return true;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
pullChildren();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
int topInset = 0;
int bottomInset = 0;
measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0);
LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams();
maxWidth = Math.max(maxWidth,
mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = ViewUtils.combineMeasuredStates(childState,
ViewCompat.getMeasuredState(mActionBarTop));
// xlarge screen layout doesn't have bottom action bar.
if (mActionBarBottom != null) {
measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
lp = (LayoutParams) mActionBarBottom.getLayoutParams();
maxWidth = Math.max(maxWidth,
mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = ViewUtils.combineMeasuredStates(childState,
ViewCompat.getMeasuredState(mActionBarBottom));
}
final int vis = ViewCompat.getWindowSystemUiVisibility(this);
final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
if (stable) {
// This is the standard space needed for the action bar. For stable measurement,
// we can't depend on the size currently reported by it -- this must remain constant.
topInset = mActionBarHeight;
if (mHasNonEmbeddedTabs) {
final View tabs = mActionBarTop.getTabContainer();
if (tabs != null) {
// If tabs are not embedded, increase space on top to account for them.
topInset += mActionBarHeight;
}
}
} else if (mActionBarTop.getVisibility() != GONE) {
// This is the space needed on top of the window for all of the action bar
// and tabs.
topInset = mActionBarTop.getMeasuredHeight();
}
if (mDecorToolbar.isSplit()) {
// If action bar is split, adjust bottom insets for it.
if (mActionBarBottom != null) {
if (stable) {
bottomInset = mActionBarHeight;
} else {
bottomInset = mActionBarBottom.getMeasuredHeight();
}
}
}
// If the window has not requested system UI layout flags, we need to
// make sure its content is not being covered by system UI... though it
// will still be covered by the action bar if they have requested it to
// overlay.
mContentInsets.set(mBaseContentInsets);
mInnerInsets.set(mBaseInnerInsets);
if (!mOverlayMode && !stable) {
mContentInsets.top += topInset;
mContentInsets.bottom += bottomInset;
} else {
mInnerInsets.top += topInset;
mInnerInsets.bottom += bottomInset;
}
applyInsets(mContent, mContentInsets, true, true, true, true);
if (!mLastInnerInsets.equals(mInnerInsets)) {
// If the inner insets have changed, we need to dispatch this down to
// the app's fitSystemWindows(). We do this before measuring the content
// view to keep the same semantics as the normal fitSystemWindows() call.
mLastInnerInsets.set(mInnerInsets);
mContent.dispatchFitSystemWindows(mInnerInsets);
}
measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
lp = (LayoutParams) mContent.getLayoutParams();
maxWidth = Math.max(maxWidth,
mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = ViewUtils.combineMeasuredStates(childState,
ViewCompat.getMeasuredState(mContent));
// Account for padding too
maxWidth += getPaddingLeft() + getPaddingRight();
maxHeight += getPaddingTop() + getPaddingBottom();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(
ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
ViewCompat.resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
final int parentLeft = getPaddingLeft();
final int parentRight = right - left - getPaddingRight();
final int parentTop = getPaddingTop();
final int parentBottom = bottom - top - getPaddingBottom();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft = parentLeft + lp.leftMargin;
int childTop;
if (child == mActionBarBottom) {
childTop = parentBottom - height - lp.bottomMargin;
} else {
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
@Override
public void draw(Canvas c) {
super.draw(c);
if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) {
final int top = mActionBarTop.getVisibility() == VISIBLE ?
(int) (mActionBarTop.getBottom() + ViewCompat.getTranslationY(mActionBarTop) + 0.5f)
: 0;
mWindowContentOverlay.setBounds(0, top, getWidth(),
top + mWindowContentOverlay.getIntrinsicHeight());
mWindowContentOverlay.draw(c);
}
}
@Override
public boolean shouldDelayChildPressedState() {
return false;
}
@Override
public boolean onStartNestedScroll(View child, View target, int axes) {
if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) {
return false;
}
return mHideOnContentScroll;
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
super.onNestedScrollAccepted(child, target, axes);
mHideOnContentScrollReference = getActionBarHideOffset();
haltActionBarHideOffsetAnimations();
if (mActionBarVisibilityCallback != null) {
mActionBarVisibilityCallback.onContentScrollStarted();
}
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
mHideOnContentScrollReference += dyConsumed;
setActionBarHideOffset(mHideOnContentScrollReference);
}
@Override
public void onStopNestedScroll(View target) {
super.onStopNestedScroll(target);
if (mHideOnContentScroll && !mAnimatingForFling) {
if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) {
postRemoveActionBarHideOffset();
} else {
postAddActionBarHideOffset();
}
}
if (mActionBarVisibilityCallback != null) {
mActionBarVisibilityCallback.onContentScrollStopped();
}
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
if (!mHideOnContentScroll || !consumed) {
return false;
}
if (shouldHideActionBarOnFling(velocityX, velocityY)) {
addActionBarHideOffset();
} else {
removeActionBarHideOffset();
}
mAnimatingForFling = true;
return true;
}
void pullChildren() {
if (mContent == null) {
mContent = (ContentFrameLayout) findViewById(R.id.action_bar_activity_content);
mActionBarTop = (ActionBarContainer) findViewById(R.id.action_bar_container);
mDecorToolbar = getDecorToolbar(findViewById(R.id.action_bar));
mActionBarBottom = (ActionBarContainer) findViewById(R.id.split_action_bar);
}
}
private DecorToolbar getDecorToolbar(View view) {
if (view instanceof DecorToolbar) {
return (DecorToolbar) view;
} else if (view instanceof Toolbar) {
return ((Toolbar) view).getWrapper();
} else {
throw new IllegalStateException("Can't make a decor toolbar out of " +
view.getClass().getSimpleName());
}
}
public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
if (hideOnContentScroll != mHideOnContentScroll) {
mHideOnContentScroll = hideOnContentScroll;
if (!hideOnContentScroll) {
if (VersionUtils.isAtLeastL()) {
stopNestedScroll();
}
haltActionBarHideOffsetAnimations();
setActionBarHideOffset(0);
}
}
}
public boolean isHideOnContentScrollEnabled() {
return mHideOnContentScroll;
}
public int getActionBarHideOffset() {
return mActionBarTop != null ? -((int) ViewCompat.getTranslationY(mActionBarTop)) : 0;
}
public void setActionBarHideOffset(int offset) {
haltActionBarHideOffsetAnimations();
final int topHeight = mActionBarTop.getHeight();
offset = Math.max(0, Math.min(offset, topHeight));
ViewCompat.setTranslationY(mActionBarTop, -offset);
if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
// Match the hide offset proportionally for a split bar
final float fOffset = (float) offset / topHeight;
final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset);
ViewCompat.setTranslationY(mActionBarBottom, bOffset);
}
}
private void haltActionBarHideOffsetAnimations() {
removeCallbacks(mRemoveActionBarHideOffset);
removeCallbacks(mAddActionBarHideOffset);
if (mCurrentActionBarTopAnimator != null) {
mCurrentActionBarTopAnimator.cancel();
}
if (mCurrentActionBarBottomAnimator != null) {
mCurrentActionBarBottomAnimator.cancel();
}
}
private void postRemoveActionBarHideOffset() {
haltActionBarHideOffsetAnimations();
postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
}
private void postAddActionBarHideOffset() {
haltActionBarHideOffsetAnimations();
postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
}
private void removeActionBarHideOffset() {
haltActionBarHideOffsetAnimations();
mRemoveActionBarHideOffset.run();
}
private void addActionBarHideOffset() {
haltActionBarHideOffsetAnimations();
mAddActionBarHideOffset.run();
}
private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) {
mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
final int finalY = mFlingEstimator.getFinalY();
return finalY > mActionBarTop.getHeight();
}
@Override
public void setWindowCallback(Window.Callback cb) {
pullChildren();
mDecorToolbar.setWindowCallback(cb);
}
@Override
public void setWindowTitle(CharSequence title) {
pullChildren();
mDecorToolbar.setWindowTitle(title);
}
@Override
public CharSequence getTitle() {
pullChildren();
return mDecorToolbar.getTitle();
}
@Override
public void initFeature(int windowFeature) {
pullChildren();
switch (windowFeature) {
case Window.FEATURE_PROGRESS:
mDecorToolbar.initProgress();
break;
case Window.FEATURE_INDETERMINATE_PROGRESS:
mDecorToolbar.initIndeterminateProgress();
break;
case Window.FEATURE_ACTION_BAR_OVERLAY:
setOverlayMode(true);
break;
}
}
@Override
public void setUiOptions(int uiOptions) {
// Split Action Bar not included.
}
@Override
public boolean hasIcon() {
pullChildren();
return mDecorToolbar.hasIcon();
}
@Override
public boolean hasLogo() {
pullChildren();
return mDecorToolbar.hasLogo();
}
@Override
public void setIcon(int resId) {
pullChildren();
mDecorToolbar.setIcon(resId);
}
@Override
public void setIcon(Drawable d) {
pullChildren();
mDecorToolbar.setIcon(d);
}
@Override
public void setLogo(int resId) {
pullChildren();
mDecorToolbar.setLogo(resId);
}
@Override
public boolean canShowOverflowMenu() {
pullChildren();
return mDecorToolbar.canShowOverflowMenu();
}
@Override
public boolean isOverflowMenuShowing() {
pullChildren();
return mDecorToolbar.isOverflowMenuShowing();
}
@Override
public boolean isOverflowMenuShowPending() {
pullChildren();
return mDecorToolbar.isOverflowMenuShowPending();
}
@Override
public boolean showOverflowMenu() {
pullChildren();
return mDecorToolbar.showOverflowMenu();
}
@Override
public boolean hideOverflowMenu() {
pullChildren();
return mDecorToolbar.hideOverflowMenu();
}
@Override
public void setMenuPrepared() {
pullChildren();
mDecorToolbar.setMenuPrepared();
}
@Override
public void setMenu(Menu menu, MenuPresenter.Callback cb) {
pullChildren();
mDecorToolbar.setMenu(menu, cb);
}
@Override
public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
pullChildren();
mDecorToolbar.saveHierarchyState(toolbarStates);
}
@Override
public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
pullChildren();
mDecorToolbar.restoreHierarchyState(toolbarStates);
}
@Override
public void dismissPopups() {
pullChildren();
mDecorToolbar.dismissPopupMenus();
}
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
}
public interface ActionBarVisibilityCallback {
void onWindowVisibilityChanged(int visibility);
void showForSystem();
void hideForSystem();
void enableContentAnimations(boolean enable);
void onContentScrollStarted();
void onContentScrollStopped();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,843 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ActionProvider;
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.support.v7.widget.LinearLayoutCompat;
import android.support.v7.widget.ListPopupWindow;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;
/**
* This class is a view for choosing an activity for handling a given {@link Intent}.
* <p>
* The view is composed of two adjacent buttons:
* <ul>
* <li>
* The left button is an immediate action and allows one click activity choosing.
* Tapping this button immediately executes the intent without requiring any further
* user input. Long press on this button shows a popup for changing the default
* activity.
* </li>
* <li>
* The right button is an overflow action and provides an optimized menu
* of additional activities. Tapping this button shows a popup anchored to this
* view, listing the most frequently used activities. This list is initially
* limited to a small number of items in frequency used order. The last item,
* "Show all..." serves as an affordance to display all available activities.
* </li>
* </ul>
* </p>
*
* @hide
*/
public class ActivityChooserView extends ViewGroup implements
ActivityChooserModel.ActivityChooserModelClient {
private static final String LOG_TAG = "ActivityChooserView";
/**
* An adapter for displaying the activities in an {@link android.widget.AdapterView}.
*/
private final ActivityChooserViewAdapter mAdapter;
/**
* Implementation of various interfaces to avoid publishing them in the APIs.
*/
private final Callbacks mCallbacks;
/**
* The content of this view.
*/
private final LinearLayoutCompat mActivityChooserContent;
/**
* Stores the background drawable to allow hiding and latter showing.
*/
private final Drawable mActivityChooserContentBackground;
/**
* The expand activities action button;
*/
private final FrameLayout mExpandActivityOverflowButton;
/**
* The image for the expand activities action button;
*/
private final ImageView mExpandActivityOverflowButtonImage;
/**
* The default activities action button;
*/
private final FrameLayout mDefaultActivityButton;
/**
* The image for the default activities action button;
*/
private final ImageView mDefaultActivityButtonImage;
/**
* The maximal width of the list popup.
*/
private final int mListPopupMaxWidth;
/**
* The ActionProvider hosting this view, if applicable.
*/
ActionProvider mProvider;
/**
* Observer for the model data.
*/
private final DataSetObserver mModelDataSetOberver = new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
mAdapter.notifyDataSetChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
mAdapter.notifyDataSetInvalidated();
}
};
private final OnGlobalLayoutListener mOnGlobalLayoutListener = new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (isShowingPopup()) {
if (!isShown()) {
getListPopupWindow().dismiss();
} else {
getListPopupWindow().show();
if (mProvider != null) {
mProvider.subUiVisibilityChanged(true);
}
}
}
}
};
/**
* Popup window for showing the activity overflow list.
*/
private ListPopupWindow mListPopupWindow;
/**
* Listener for the dismissal of the popup/alert.
*/
private PopupWindow.OnDismissListener mOnDismissListener;
/**
* Flag whether a default activity currently being selected.
*/
private boolean mIsSelectingDefaultActivity;
/**
* The count of activities in the popup.
*/
private int mInitialActivityCount = ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT;
/**
* Flag whether this view is attached to a window.
*/
private boolean mIsAttachedToWindow;
/**
* String resource for formatting content description of the default target.
*/
private int mDefaultActionButtonContentDescription;
/**
* Create a new instance.
*
* @param context The application environment.
*/
public ActivityChooserView(Context context) {
this(context, null);
}
/**
* Create a new instance.
*
* @param context The application environment.
* @param attrs A collection of attributes.
*/
public ActivityChooserView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Create a new instance.
*
* @param context The application environment.
* @param attrs A collection of attributes.
* @param defStyle The default style to apply to this view.
*/
public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray attributesArray = context.obtainStyledAttributes(attrs,
R.styleable.ActivityChooserView, defStyle, 0);
mInitialActivityCount = attributesArray.getInt(
R.styleable.ActivityChooserView_initialActivityCount,
ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT);
Drawable expandActivityOverflowButtonDrawable = attributesArray.getDrawable(
R.styleable.ActivityChooserView_expandActivityOverflowButtonDrawable);
attributesArray.recycle();
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.abc_activity_chooser_view, this, true);
mCallbacks = new Callbacks();
mActivityChooserContent = (LinearLayoutCompat) findViewById(R.id.activity_chooser_view_content);
mActivityChooserContentBackground = mActivityChooserContent.getBackground();
mDefaultActivityButton = (FrameLayout) findViewById(R.id.default_activity_button);
mDefaultActivityButton.setOnClickListener(mCallbacks);
mDefaultActivityButton.setOnLongClickListener(mCallbacks);
mDefaultActivityButtonImage = (ImageView) mDefaultActivityButton.findViewById(R.id.image);
final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button);
expandButton.setOnClickListener(mCallbacks);
expandButton.setOnTouchListener(new ListPopupWindow.ForwardingListener(expandButton) {
@Override
public ListPopupWindow getPopup() {
return getListPopupWindow();
}
@Override
protected boolean onForwardingStarted() {
showPopup();
return true;
}
@Override
protected boolean onForwardingStopped() {
dismissPopup();
return true;
}
});
mExpandActivityOverflowButton = expandButton;
mExpandActivityOverflowButtonImage =
(ImageView) expandButton.findViewById(R.id.image);
mExpandActivityOverflowButtonImage.setImageDrawable(expandActivityOverflowButtonDrawable);
mAdapter = new ActivityChooserViewAdapter();
mAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
updateAppearance();
}
});
Resources resources = context.getResources();
mListPopupMaxWidth = Math.max(resources.getDisplayMetrics().widthPixels / 2,
resources.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
}
/**
* {@inheritDoc}
*/
public void setActivityChooserModel(ActivityChooserModel dataModel) {
mAdapter.setDataModel(dataModel);
if (isShowingPopup()) {
dismissPopup();
showPopup();
}
}
/**
* Sets the background for the button that expands the activity
* overflow list.
*
* <strong>Note:</strong> Clients would like to set this drawable
* as a clue about the action the chosen activity will perform. For
* example, if a share activity is to be chosen the drawable should
* give a clue that sharing is to be performed.
*
* @param drawable The drawable.
*/
public void setExpandActivityOverflowButtonDrawable(Drawable drawable) {
mExpandActivityOverflowButtonImage.setImageDrawable(drawable);
}
/**
* Sets the content description for the button that expands the activity
* overflow list.
*
* description as a clue about the action performed by the button.
* For example, if a share activity is to be chosen the content
* description should be something like "Share with".
*
* @param resourceId The content description resource id.
*/
public void setExpandActivityOverflowButtonContentDescription(int resourceId) {
CharSequence contentDescription = getContext().getString(resourceId);
mExpandActivityOverflowButtonImage.setContentDescription(contentDescription);
}
/**
* Set the provider hosting this view, if applicable.
* @hide Internal use only
*/
public void setProvider(ActionProvider provider) {
mProvider = provider;
}
/**
* Shows the popup window with activities.
*
* @return True if the popup was shown, false if already showing.
*/
public boolean showPopup() {
if (isShowingPopup() || !mIsAttachedToWindow) {
return false;
}
mIsSelectingDefaultActivity = false;
showPopupUnchecked(mInitialActivityCount);
return true;
}
/**
* Shows the popup no matter if it was already showing.
*
* @param maxActivityCount The max number of activities to display.
*/
private void showPopupUnchecked(int maxActivityCount) {
if (mAdapter.getDataModel() == null) {
throw new IllegalStateException("No data model. Did you call #setDataModel?");
}
getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
final boolean defaultActivityButtonShown =
mDefaultActivityButton.getVisibility() == VISIBLE;
final int activityCount = mAdapter.getActivityCount();
final int maxActivityCountOffset = defaultActivityButtonShown ? 1 : 0;
if (maxActivityCount != ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED
&& activityCount > maxActivityCount + maxActivityCountOffset) {
mAdapter.setShowFooterView(true);
mAdapter.setMaxActivityCount(maxActivityCount - 1);
} else {
mAdapter.setShowFooterView(false);
mAdapter.setMaxActivityCount(maxActivityCount);
}
ListPopupWindow popupWindow = getListPopupWindow();
if (!popupWindow.isShowing()) {
if (mIsSelectingDefaultActivity || !defaultActivityButtonShown) {
mAdapter.setShowDefaultActivity(true, defaultActivityButtonShown);
} else {
mAdapter.setShowDefaultActivity(false, false);
}
final int contentWidth = Math.min(mAdapter.measureContentWidth(), mListPopupMaxWidth);
popupWindow.setContentWidth(contentWidth);
popupWindow.show();
if (mProvider != null) {
mProvider.subUiVisibilityChanged(true);
}
popupWindow.getListView().setContentDescription(getContext().getString(
R.string.abc_activitychooserview_choose_application));
}
}
/**
* Dismisses the popup window with activities.
*
* @return True if dismissed, false if already dismissed.
*/
public boolean dismissPopup() {
if (isShowingPopup()) {
getListPopupWindow().dismiss();
ViewTreeObserver viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
}
}
return true;
}
/**
* Gets whether the popup window with activities is shown.
*
* @return True if the popup is shown.
*/
public boolean isShowingPopup() {
return getListPopupWindow().isShowing();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ActivityChooserModel dataModel = mAdapter.getDataModel();
if (dataModel != null) {
dataModel.registerObserver(mModelDataSetOberver);
}
mIsAttachedToWindow = true;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
ActivityChooserModel dataModel = mAdapter.getDataModel();
if (dataModel != null) {
dataModel.unregisterObserver(mModelDataSetOberver);
}
ViewTreeObserver viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
}
if (isShowingPopup()) {
dismissPopup();
}
mIsAttachedToWindow = false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View child = mActivityChooserContent;
// If the default action is not visible we want to be as tall as the
// ActionBar so if this widget is used in the latter it will look as
// a normal action button.
if (mDefaultActivityButton.getVisibility() != VISIBLE) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
MeasureSpec.EXACTLY);
}
measureChild(child, widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight());
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mActivityChooserContent.layout(0, 0, right - left, bottom - top);
if (!isShowingPopup()) {
dismissPopup();
}
}
public ActivityChooserModel getDataModel() {
return mAdapter.getDataModel();
}
/**
* Sets a listener to receive a callback when the popup is dismissed.
*
* @param listener The listener to be notified.
*/
public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
mOnDismissListener = listener;
}
/**
* Sets the initial count of items shown in the activities popup
* i.e. the items before the popup is expanded. This is an upper
* bound since it is not guaranteed that such number of intent
* handlers exist.
*
* @param itemCount The initial popup item count.
*/
public void setInitialActivityCount(int itemCount) {
mInitialActivityCount = itemCount;
}
/**
* Sets a content description of the default action button. This
* resource should be a string taking one formatting argument and
* will be used for formatting the content description of the button
* dynamically as the default target changes. For example, a resource
* pointing to the string "share with %1$s" will result in a content
* description "share with Bluetooth" for the Bluetooth activity.
*
* @param resourceId The resource id.
*/
public void setDefaultActionButtonContentDescription(int resourceId) {
mDefaultActionButtonContentDescription = resourceId;
}
/**
* Gets the list popup window which is lazily initialized.
*
* @return The popup.
*/
private ListPopupWindow getListPopupWindow() {
if (mListPopupWindow == null) {
mListPopupWindow = new ListPopupWindow(getContext());
mListPopupWindow.setAdapter(mAdapter);
mListPopupWindow.setAnchorView(ActivityChooserView.this);
mListPopupWindow.setModal(true);
mListPopupWindow.setOnItemClickListener(mCallbacks);
mListPopupWindow.setOnDismissListener(mCallbacks);
}
return mListPopupWindow;
}
/**
* Updates the buttons state.
*/
private void updateAppearance() {
// Expand overflow button.
if (mAdapter.getCount() > 0) {
mExpandActivityOverflowButton.setEnabled(true);
} else {
mExpandActivityOverflowButton.setEnabled(false);
}
// Default activity button.
final int activityCount = mAdapter.getActivityCount();
final int historySize = mAdapter.getHistorySize();
if (activityCount==1 || activityCount > 1 && historySize > 0) {
mDefaultActivityButton.setVisibility(VISIBLE);
ResolveInfo activity = mAdapter.getDefaultActivity();
PackageManager packageManager = getContext().getPackageManager();
mDefaultActivityButtonImage.setImageDrawable(activity.loadIcon(packageManager));
if (mDefaultActionButtonContentDescription != 0) {
CharSequence label = activity.loadLabel(packageManager);
String contentDescription = getContext().getString(
mDefaultActionButtonContentDescription, label);
mDefaultActivityButton.setContentDescription(contentDescription);
}
} else {
mDefaultActivityButton.setVisibility(View.GONE);
}
// Activity chooser content.
if (mDefaultActivityButton.getVisibility() == VISIBLE) {
mActivityChooserContent.setBackgroundDrawable(mActivityChooserContentBackground);
} else {
mActivityChooserContent.setBackgroundDrawable(null);
}
}
/**
* Interface implementation to avoid publishing them in the APIs.
*/
private class Callbacks implements AdapterView.OnItemClickListener,
View.OnClickListener, View.OnLongClickListener, PopupWindow.OnDismissListener {
// AdapterView#OnItemClickListener
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ActivityChooserViewAdapter adapter = (ActivityChooserViewAdapter) parent.getAdapter();
final int itemViewType = adapter.getItemViewType(position);
switch (itemViewType) {
case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_FOOTER: {
showPopupUnchecked(ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED);
} break;
case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_ACTIVITY: {
dismissPopup();
if (mIsSelectingDefaultActivity) {
// The item at position zero is the default already.
if (position > 0) {
mAdapter.getDataModel().setDefaultActivity(position);
}
} else {
// If the default target is not shown in the list, the first
// item in the model is default action => adjust index
position = mAdapter.getShowDefaultActivity() ? position : position + 1;
Intent launchIntent = mAdapter.getDataModel().chooseActivity(position);
if (launchIntent != null) {
launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
getContext().startActivity(launchIntent);
}
}
} break;
default:
throw new IllegalArgumentException();
}
}
// View.OnClickListener
public void onClick(View view) {
if (view == mDefaultActivityButton) {
dismissPopup();
ResolveInfo defaultActivity = mAdapter.getDefaultActivity();
final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity);
Intent launchIntent = mAdapter.getDataModel().chooseActivity(index);
if (launchIntent != null) {
launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
getContext().startActivity(launchIntent);
}
} else if (view == mExpandActivityOverflowButton) {
mIsSelectingDefaultActivity = false;
showPopupUnchecked(mInitialActivityCount);
} else {
throw new IllegalArgumentException();
}
}
// OnLongClickListener#onLongClick
@Override
public boolean onLongClick(View view) {
if (view == mDefaultActivityButton) {
if (mAdapter.getCount() > 0) {
mIsSelectingDefaultActivity = true;
showPopupUnchecked(mInitialActivityCount);
}
} else {
throw new IllegalArgumentException();
}
return true;
}
// PopUpWindow.OnDismissListener#onDismiss
public void onDismiss() {
notifyOnDismissListener();
if (mProvider != null) {
mProvider.subUiVisibilityChanged(false);
}
}
private void notifyOnDismissListener() {
if (mOnDismissListener != null) {
mOnDismissListener.onDismiss();
}
}
}
/**
* Adapter for backing the list of activities shown in the popup.
*/
private class ActivityChooserViewAdapter extends BaseAdapter {
public static final int MAX_ACTIVITY_COUNT_UNLIMITED = Integer.MAX_VALUE;
public static final int MAX_ACTIVITY_COUNT_DEFAULT = 4;
private static final int ITEM_VIEW_TYPE_ACTIVITY = 0;
private static final int ITEM_VIEW_TYPE_FOOTER = 1;
private static final int ITEM_VIEW_TYPE_COUNT = 3;
private ActivityChooserModel mDataModel;
private int mMaxActivityCount = MAX_ACTIVITY_COUNT_DEFAULT;
private boolean mShowDefaultActivity;
private boolean mHighlightDefaultActivity;
private boolean mShowFooterView;
public void setDataModel(ActivityChooserModel dataModel) {
ActivityChooserModel oldDataModel = mAdapter.getDataModel();
if (oldDataModel != null && isShown()) {
oldDataModel.unregisterObserver(mModelDataSetOberver);
}
mDataModel = dataModel;
if (dataModel != null && isShown()) {
dataModel.registerObserver(mModelDataSetOberver);
}
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
if (mShowFooterView && position == getCount() - 1) {
return ITEM_VIEW_TYPE_FOOTER;
} else {
return ITEM_VIEW_TYPE_ACTIVITY;
}
}
@Override
public int getViewTypeCount() {
return ITEM_VIEW_TYPE_COUNT;
}
public int getCount() {
int count = 0;
int activityCount = mDataModel.getActivityCount();
if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
activityCount--;
}
count = Math.min(activityCount, mMaxActivityCount);
if (mShowFooterView) {
count++;
}
return count;
}
public Object getItem(int position) {
final int itemViewType = getItemViewType(position);
switch (itemViewType) {
case ITEM_VIEW_TYPE_FOOTER:
return null;
case ITEM_VIEW_TYPE_ACTIVITY:
if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
position++;
}
return mDataModel.getActivity(position);
default:
throw new IllegalArgumentException();
}
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
final int itemViewType = getItemViewType(position);
switch (itemViewType) {
case ITEM_VIEW_TYPE_FOOTER:
if (convertView == null || convertView.getId() != ITEM_VIEW_TYPE_FOOTER) {
convertView = LayoutInflater.from(getContext()).inflate(
R.layout.abc_activity_chooser_view_list_item, parent, false);
convertView.setId(ITEM_VIEW_TYPE_FOOTER);
TextView titleView = (TextView) convertView.findViewById(R.id.title);
titleView.setText(getContext().getString(
R.string.abc_activity_chooser_view_see_all));
}
return convertView;
case ITEM_VIEW_TYPE_ACTIVITY:
if (convertView == null || convertView.getId() != R.id.list_item) {
convertView = LayoutInflater.from(getContext()).inflate(
R.layout.abc_activity_chooser_view_list_item, parent, false);
}
PackageManager packageManager = getContext().getPackageManager();
// Set the icon
ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
ResolveInfo activity = (ResolveInfo) getItem(position);
iconView.setImageDrawable(activity.loadIcon(packageManager));
// Set the title.
TextView titleView = (TextView) convertView.findViewById(R.id.title);
titleView.setText(activity.loadLabel(packageManager));
// Highlight the default.
if (mShowDefaultActivity && position == 0 && mHighlightDefaultActivity) {
ViewCompat.setActivated(convertView, true);
} else {
ViewCompat.setActivated(convertView, false);
}
return convertView;
default:
throw new IllegalArgumentException();
}
}
public int measureContentWidth() {
// The user may have specified some of the target not to be shown but we
// want to measure all of them since after expansion they should fit.
final int oldMaxActivityCount = mMaxActivityCount;
mMaxActivityCount = MAX_ACTIVITY_COUNT_UNLIMITED;
int contentWidth = 0;
View itemView = null;
final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final int count = getCount();
for (int i = 0; i < count; i++) {
itemView = getView(i, itemView, null);
itemView.measure(widthMeasureSpec, heightMeasureSpec);
contentWidth = Math.max(contentWidth, itemView.getMeasuredWidth());
}
mMaxActivityCount = oldMaxActivityCount;
return contentWidth;
}
public void setMaxActivityCount(int maxActivityCount) {
if (mMaxActivityCount != maxActivityCount) {
mMaxActivityCount = maxActivityCount;
notifyDataSetChanged();
}
}
public ResolveInfo getDefaultActivity() {
return mDataModel.getDefaultActivity();
}
public void setShowFooterView(boolean showFooterView) {
if (mShowFooterView != showFooterView) {
mShowFooterView = showFooterView;
notifyDataSetChanged();
}
}
public int getActivityCount() {
return mDataModel.getActivityCount();
}
public int getHistorySize() {
return mDataModel.getHistorySize();
}
public ActivityChooserModel getDataModel() {
return mDataModel;
}
public void setShowDefaultActivity(boolean showDefaultActivity,
boolean highlightDefaultActivity) {
if (mShowDefaultActivity != showDefaultActivity
|| mHighlightDefaultActivity != highlightDefaultActivity) {
mShowDefaultActivity = showDefaultActivity;
mHighlightDefaultActivity = highlightDefaultActivity;
notifyDataSetChanged();
}
}
public boolean getShowDefaultActivity() {
return mShowDefaultActivity;
}
}
/**
* Allows us to set the background using TintTypedArray
* @hide
*/
public static class InnerLayout extends LinearLayoutCompat {
private static final int[] TINT_ATTRS = {
android.R.attr.background
};
public InnerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS);
setBackgroundDrawable(a.getDrawable(0));
a.recycle();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,120 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.widget.PopupWindow;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
/**
* @hide
*/
public class AppCompatPopupWindow extends PopupWindow {
private static final String TAG = "AppCompatPopupWindow";
private final boolean mOverlapAnchor;
public AppCompatPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
R.styleable.PopupWindow, defStyleAttr, 0);
mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
// We re-set this for tinting purposes
setBackgroundDrawable(a.getDrawable(R.styleable.PopupWindow_android_popupBackground));
a.recycle();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// For devices pre-ICS, we need to wrap the internal OnScrollChangedListener
// due to NPEs.
wrapOnScrollChangedListener(this);
}
}
@Override
public void showAsDropDown(View anchor, int xoff, int yoff) {
if (Build.VERSION.SDK_INT < 21 && mOverlapAnchor) {
// If we're pre-L, emulate overlapAnchor by modifying the yOff
yoff -= anchor.getHeight();
}
super.showAsDropDown(anchor, xoff, yoff);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
if (Build.VERSION.SDK_INT < 21 && mOverlapAnchor) {
// If we're pre-L, emulate overlapAnchor by modifying the yOff
yoff -= anchor.getHeight();
}
super.showAsDropDown(anchor, xoff, yoff, gravity);
}
@Override
public void update(View anchor, int xoff, int yoff, int width, int height) {
if (Build.VERSION.SDK_INT < 21 && mOverlapAnchor) {
// If we're pre-L, emulate overlapAnchor by modifying the yOff
yoff -= anchor.getHeight();
}
super.update(anchor, xoff, yoff, width, height);
}
private static void wrapOnScrollChangedListener(final PopupWindow popup) {
try {
final Field fieldAnchor = PopupWindow.class.getDeclaredField("mAnchor");
fieldAnchor.setAccessible(true);
final Field fieldListener = PopupWindow.class
.getDeclaredField("mOnScrollChangedListener");
fieldListener.setAccessible(true);
final OnScrollChangedListener originalListener =
(OnScrollChangedListener) fieldListener.get(popup);
// Now set a new listener, wrapping the original and only proxying the call when
// we have an anchor view.
fieldListener.set(popup, new OnScrollChangedListener() {
@Override
public void onScrollChanged() {
try {
WeakReference<View> mAnchor = (WeakReference<View>) fieldAnchor.get(popup);
if (mAnchor == null || mAnchor.get() == null) {
return;
} else {
originalListener.onScrollChanged();
}
} catch (IllegalAccessException e) {
// Oh well...
}
}
});
} catch (Exception e) {
Log.d(TAG, "Exception while installing workaround OnScrollChangedListener", e);
}
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.support.v7.appcompat.R;
import android.support.v7.internal.text.AllCapsTransformationMethod;
import android.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import java.util.Locale;
/**
* @hide
*/
public class CompatTextView extends TextView {
public CompatTextView(Context context) {
this(context, null);
}
public CompatTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CompatTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// First read the TextAppearance style id
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView,
defStyle, 0);
final int ap = a.getResourceId(R.styleable.CompatTextView_android_textAppearance, -1);
a.recycle();
// Now check TextAppearance's textAllCaps value
if (ap != -1) {
TypedArray appearance = context.obtainStyledAttributes(ap, R.styleable.TextAppearance);
if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) {
setAllCaps(appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false));
}
appearance.recycle();
}
// Now read the style's value
a = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView, defStyle, 0);
if (a.hasValue(R.styleable.CompatTextView_textAllCaps)) {
setAllCaps(a.getBoolean(R.styleable.CompatTextView_textAllCaps, false));
}
a.recycle();
}
public void setAllCaps(boolean allCaps) {
setTransformationMethod(allCaps ? new AllCapsTransformationMethod(getContext()) : null);
}
@Override
public void setTextAppearance(Context context, int resid) {
super.setTextAppearance(context, resid);
TypedArray appearance = context.obtainStyledAttributes(resid, R.styleable.TextAppearance);
if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) {
setAllCaps(appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false));
}
appearance.recycle();
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.FrameLayout;
/**
* @hide
*/
public class ContentFrameLayout extends FrameLayout {
public ContentFrameLayout(Context context) {
this(context, null);
}
public ContentFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ContentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* @hide
*/
public void dispatchFitSystemWindows(Rect insets) {
fitSystemWindows(insets);
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.graphics.drawable.Drawable;
import android.os.Parcelable;
import android.support.v7.internal.view.menu.MenuPresenter;
import android.util.SparseArray;
import android.view.Menu;
import android.view.Window;
/**
* Implemented by the top-level decor layout for a window. DecorContentParent offers
* entry points for a number of title/window decor features.
*
* @hide
*/
public interface DecorContentParent {
void setWindowCallback(Window.Callback cb);
void setWindowTitle(CharSequence title);
CharSequence getTitle();
void initFeature(int windowFeature);
void setUiOptions(int uiOptions);
boolean hasIcon();
boolean hasLogo();
void setIcon(int resId);
void setIcon(Drawable d);
void setLogo(int resId);
boolean canShowOverflowMenu();
boolean isOverflowMenuShowing();
boolean isOverflowMenuShowPending();
boolean showOverflowMenu();
boolean hideOverflowMenu();
void setMenuPrepared();
void setMenu(Menu menu, MenuPresenter.Callback cb);
void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates);
void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates);
void dismissPopups();
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Parcelable;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.view.menu.MenuPresenter;
import android.util.SparseArray;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.SpinnerAdapter;
/**
* Common interface for a toolbar that sits as part of the window decor.
* Layouts that control window decor use this as a point of interaction with different
* bar implementations.
*
* @hide
*/
public interface DecorToolbar {
ViewGroup getViewGroup();
Context getContext();
boolean isSplit();
boolean hasExpandedActionView();
void collapseActionView();
void setWindowCallback(Window.Callback cb);
void setWindowTitle(CharSequence title);
CharSequence getTitle();
void setTitle(CharSequence title);
CharSequence getSubtitle();
void setSubtitle(CharSequence subtitle);
void initProgress();
void initIndeterminateProgress();
boolean canSplit();
void setSplitView(ViewGroup splitView);
void setSplitToolbar(boolean split);
void setSplitWhenNarrow(boolean splitWhenNarrow);
boolean hasIcon();
boolean hasLogo();
void setIcon(int resId);
void setIcon(Drawable d);
void setLogo(int resId);
void setLogo(Drawable d);
boolean canShowOverflowMenu();
boolean isOverflowMenuShowing();
boolean isOverflowMenuShowPending();
boolean showOverflowMenu();
boolean hideOverflowMenu();
void setMenuPrepared();
void setMenu(Menu menu, MenuPresenter.Callback cb);
void dismissPopupMenus();
int getDisplayOptions();
void setDisplayOptions(int opts);
void setEmbeddedTabView(ScrollingTabContainerView tabView);
boolean hasEmbeddedTabs();
boolean isTitleTruncated();
void setCollapsible(boolean collapsible);
void setHomeButtonEnabled(boolean enable);
int getNavigationMode();
void setNavigationMode(int mode);
void setDropdownParams(SpinnerAdapter adapter, AdapterViewCompat.OnItemSelectedListener listener);
void setDropdownSelectedPosition(int position);
int getDropdownSelectedPosition();
int getDropdownItemCount();
void setCustomView(View view);
View getCustomView();
void animateToVisibility(int visibility);
void setNavigationIcon(Drawable icon);
void setNavigationIcon(int resId);
void setNavigationContentDescription(CharSequence description);
void setNavigationContentDescription(int resId);
void setDefaultNavigationContentDescription(int defaultNavigationContentDescription);
void setDefaultNavigationIcon(Drawable icon);
void saveHierarchyState(SparseArray<Parcelable> toolbarStates);
void restoreHierarchyState(SparseArray<Parcelable> toolbarStates);
void setBackgroundDrawable(Drawable d);
int getHeight();
void setVisibility(int visible);
int getVisibility();
void setMenuCallbacks(MenuPresenter.Callback presenterCallback,
MenuBuilder.Callback menuBuilderCallback);
Menu getMenu();
int getPopupTheme();
}

View file

@ -0,0 +1,99 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @hide
*/
public class DrawableUtils {
private static final String TAG = "DrawableUtils";
public static final Rect INSETS_NONE = new Rect();
private static Class<?> sInsetsClazz;
static {
if (Build.VERSION.SDK_INT >= 18) {
try {
sInsetsClazz = Class.forName("android.graphics.Insets");
} catch (ClassNotFoundException e) {
// Oh well...
}
}
}
private DrawableUtils() {}
/**
* Allows us to get the optical insets for a {@link Drawable}. Since this is hidden we need to
* use reflection. Since the {@code Insets} class is hidden also, we return a Rect instead.
*/
public static Rect getOpticalBounds(Drawable drawable) {
if (sInsetsClazz != null) {
try {
// If the Drawable is wrapped, we need to manually unwrap it and process
// the wrapped drawable.
drawable = DrawableCompat.unwrap(drawable);
final Method getOpticalInsetsMethod = drawable.getClass()
.getMethod("getOpticalInsets");
final Object insets = getOpticalInsetsMethod.invoke(drawable);
if (insets != null) {
// If the drawable has some optical insets, let's copy them into a Rect
final Rect result = new Rect();
for (Field field : sInsetsClazz.getFields()) {
switch (field.getName()) {
case "left":
result.left = field.getInt(insets);
break;
case "top":
result.top = field.getInt(insets);
break;
case "right":
result.right = field.getInt(insets);
break;
case "bottom":
result.bottom = field.getInt(insets);
break;
}
}
return result;
}
} catch (Exception e) {
// Eugh, we hit some kind of reflection issue...
Log.e(TAG, "Couldn't obtain the optical insets. Ignoring.");
}
}
// If we reach here, either we're running on a device pre-v18, the Drawable didn't have
// any optical insets, or a reflection issue, so we'll just return an empty rect
return INSETS_NONE;
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.FrameLayout;
/**
* @hide
*/
public class FitWindowsFrameLayout extends FrameLayout implements FitWindowsViewGroup {
private OnFitSystemWindowsListener mListener;
public FitWindowsFrameLayout(Context context) {
super(context);
}
public FitWindowsFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setOnFitSystemWindowsListener(OnFitSystemWindowsListener listener) {
mListener = listener;
}
@Override
protected boolean fitSystemWindows(Rect insets) {
if (mListener != null) {
mListener.onFitSystemWindows(insets);
}
return super.fitSystemWindows(insets);
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.LinearLayout;
/**
* @hide
*/
public class FitWindowsLinearLayout extends LinearLayout implements FitWindowsViewGroup {
private OnFitSystemWindowsListener mListener;
public FitWindowsLinearLayout(Context context) {
super(context);
}
public FitWindowsLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setOnFitSystemWindowsListener(OnFitSystemWindowsListener listener) {
mListener = listener;
}
@Override
protected boolean fitSystemWindows(Rect insets) {
if (mListener != null) {
mListener.onFitSystemWindows(insets);
}
return super.fitSystemWindows(insets);
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.graphics.Rect;
/**
* @hide
*/
public interface FitWindowsViewGroup {
public interface OnFitSystemWindowsListener {
void onFitSystemWindows(Rect insets);
}
public void setOnFitSystemWindowsListener(OnFitSystemWindowsListener listener);
}

View file

@ -0,0 +1,388 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.graphics.drawable.DrawableWrapper;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListAdapter;
import android.widget.ListView;
import java.lang.reflect.Field;
/**
* This class contains a number of useful things for ListView. Mainly used by
* {@link android.support.v7.widget.ListPopupWindow}.
*
* @hide
*/
public class ListViewCompat extends ListView {
public static final int INVALID_POSITION = -1;
public static final int NO_POSITION = -1;
private static final int[] STATE_SET_NOTHING = new int[] { 0 };
final Rect mSelectorRect = new Rect();
int mSelectionLeftPadding = 0;
int mSelectionTopPadding = 0;
int mSelectionRightPadding = 0;
int mSelectionBottomPadding = 0;
private Field mIsChildViewEnabled;
private GateKeeperDrawable mSelector;
public ListViewCompat(Context context) {
this(context, null);
}
public ListViewCompat(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ListViewCompat(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
try {
mIsChildViewEnabled = AbsListView.class.getDeclaredField("mIsChildViewEnabled");
mIsChildViewEnabled.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
@Override
public void setSelector(Drawable sel) {
mSelector = sel != null ? new GateKeeperDrawable(sel) : null;
super.setSelector(mSelector);
final Rect padding = new Rect();
if (sel != null) {
sel.getPadding(padding);
}
mSelectionLeftPadding = padding.left;
mSelectionTopPadding = padding.top;
mSelectionRightPadding = padding.right;
mSelectionBottomPadding = padding.bottom;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
setSelectorEnabled(true);
updateSelectorStateCompat();
}
@Override
protected void dispatchDraw(Canvas canvas) {
final boolean drawSelectorOnTop = false;
if (!drawSelectorOnTop) {
drawSelectorCompat(canvas);
}
super.dispatchDraw(canvas);
}
protected void updateSelectorStateCompat() {
Drawable selector = getSelector();
if (selector != null && shouldShowSelectorCompat()) {
selector.setState(getDrawableState());
}
}
protected boolean shouldShowSelectorCompat() {
return touchModeDrawsInPressedStateCompat() && isPressed();
}
protected boolean touchModeDrawsInPressedStateCompat() {
return false;
}
protected void drawSelectorCompat(Canvas canvas) {
if (!mSelectorRect.isEmpty()) {
final Drawable selector = getSelector();
if (selector != null) {
selector.setBounds(mSelectorRect);
selector.draw(canvas);
}
}
}
/**
* Find a position that can be selected (i.e., is not a separator).
*
* @param position The starting position to look at.
* @param lookDown Whether to look down for other positions.
* @return The next selectable position starting at position and then searching either up or
* down. Returns {@link #INVALID_POSITION} if nothing can be found.
*/
public int lookForSelectablePosition(int position, boolean lookDown) {
final ListAdapter adapter = getAdapter();
if (adapter == null || isInTouchMode()) {
return INVALID_POSITION;
}
final int count = adapter.getCount();
if (!getAdapter().areAllItemsEnabled()) {
if (lookDown) {
position = Math.max(0, position);
while (position < count && !adapter.isEnabled(position)) {
position++;
}
} else {
position = Math.min(position, count - 1);
while (position >= 0 && !adapter.isEnabled(position)) {
position--;
}
}
if (position < 0 || position >= count) {
return INVALID_POSITION;
}
return position;
} else {
if (position < 0 || position >= count) {
return INVALID_POSITION;
}
return position;
}
}
protected void positionSelectorLikeTouchCompat(int position, View sel, float x, float y) {
positionSelectorLikeFocusCompat(position, sel);
Drawable selector = getSelector();
if (selector != null && position != INVALID_POSITION) {
DrawableCompat.setHotspot(selector, x, y);
}
}
protected void positionSelectorLikeFocusCompat(int position, View sel) {
// If we're changing position, update the visibility since the selector
// is technically being detached from the previous selection.
final Drawable selector = getSelector();
final boolean manageState = selector != null && position != INVALID_POSITION;
if (manageState) {
selector.setVisible(false, false);
}
positionSelectorCompat(position, sel);
if (manageState) {
final Rect bounds = mSelectorRect;
final float x = bounds.exactCenterX();
final float y = bounds.exactCenterY();
selector.setVisible(getVisibility() == VISIBLE, false);
DrawableCompat.setHotspot(selector, x, y);
}
}
protected void positionSelectorCompat(int position, View sel) {
final Rect selectorRect = mSelectorRect;
selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
// Adjust for selection padding.
selectorRect.left -= mSelectionLeftPadding;
selectorRect.top -= mSelectionTopPadding;
selectorRect.right += mSelectionRightPadding;
selectorRect.bottom += mSelectionBottomPadding;
try {
// AbsListView.mIsChildViewEnabled controls the selector's state so we need to
// modify it's value
final boolean isChildViewEnabled = mIsChildViewEnabled.getBoolean(this);
if (sel.isEnabled() != isChildViewEnabled) {
mIsChildViewEnabled.set(this, !isChildViewEnabled);
if (position != INVALID_POSITION) {
refreshDrawableState();
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Measures the height of the given range of children (inclusive) and returns the height
* with this ListView's padding and divider heights included. If maxHeight is provided, the
* measuring will stop when the current height reaches maxHeight.
*
* @param widthMeasureSpec The width measure spec to be given to a child's
* {@link View#measure(int, int)}.
* @param startPosition The position of the first child to be shown.
* @param endPosition The (inclusive) position of the last child to be
* shown. Specify {@link #NO_POSITION} if the last child
* should be the last available child from the adapter.
* @param maxHeight The maximum height that will be returned (if all the
* children don't fit in this value, this value will be
* returned).
* @param disallowPartialChildPosition In general, whether the returned height should only
* contain entire children. This is more powerful--it is
* the first inclusive position at which partial
* children will not be allowed. Example: it looks nice
* to have at least 3 completely visible children, and
* in portrait this will most likely fit; but in
* landscape there could be times when even 2 children
* can not be completely shown, so a value of 2
* (remember, inclusive) would be good (assuming
* startPosition is 0).
* @return The height of this ListView with the given children.
*/
public int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition,
int endPosition, final int maxHeight,
int disallowPartialChildPosition) {
final int paddingTop = getListPaddingTop();
final int paddingBottom = getListPaddingBottom();
final int paddingLeft = getListPaddingLeft();
final int paddingRight = getListPaddingRight();
final int reportedDividerHeight = getDividerHeight();
final Drawable divider = getDivider();
final ListAdapter adapter = getAdapter();
if (adapter == null) {
return paddingTop + paddingBottom;
}
// Include the padding of the list
int returnedHeight = paddingTop + paddingBottom;
final int dividerHeight = ((reportedDividerHeight > 0) && divider != null)
? reportedDividerHeight : 0;
// The previous height value that was less than maxHeight and contained
// no partial children
int prevHeightWithoutPartialChild = 0;
View child = null;
int viewType = 0;
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
int newType = adapter.getItemViewType(i);
if (newType != viewType) {
child = null;
viewType = newType;
}
child = adapter.getView(i, child, this);
// Compute child height spec
int heightMeasureSpec;
final ViewGroup.LayoutParams childLp = child.getLayoutParams();
if (childLp != null && childLp.height > 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(childLp.height,
MeasureSpec.EXACTLY);
} else {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(widthMeasureSpec, heightMeasureSpec);
if (i > 0) {
// Count the divider for all but one child
returnedHeight += dividerHeight;
}
returnedHeight += child.getMeasuredHeight();
if (returnedHeight >= maxHeight) {
// We went over, figure out which height to return. If returnedHeight >
// maxHeight, then the i'th position did not fit completely.
return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
&& (i > disallowPartialChildPosition) // We've past the min pos
&& (prevHeightWithoutPartialChild > 0) // We have a prev height
&& (returnedHeight != maxHeight) // i'th child did not fit completely
? prevHeightWithoutPartialChild
: maxHeight;
}
if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
prevHeightWithoutPartialChild = returnedHeight;
}
}
// At this point, we went through the range of children, and they each
// completely fit, so return the returnedHeight
return returnedHeight;
}
protected void setSelectorEnabled(boolean enabled) {
if (mSelector != null) {
mSelector.setEnabled(enabled);
}
}
private static class GateKeeperDrawable extends DrawableWrapper {
private boolean mEnabled;
public GateKeeperDrawable(Drawable drawable) {
super(drawable);
mEnabled = true;
}
void setEnabled(boolean enabled) {
mEnabled = enabled;
}
@Override
public boolean setState(int[] stateSet) {
if (mEnabled) {
return super.setState(stateSet);
}
return false;
}
@Override
public void draw(Canvas canvas) {
if (mEnabled) {
super.draw(canvas);
}
}
@Override
public void setHotspot(float x, float y) {
if (mEnabled) {
super.setHotspot(x, y);
}
}
@Override
public void setHotspotBounds(int left, int top, int right, int bottom) {
if (mEnabled) {
super.setHotspotBounds(left, top, right, bottom);
}
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
if (mEnabled) {
return super.setVisible(visible, restart);
}
return false;
}
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.View;
/**
* @hide
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class NativeActionModeAwareLayout extends ContentFrameLayout {
private OnActionModeForChildListener mActionModeForChildListener;
public NativeActionModeAwareLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setActionModeForChildListener(OnActionModeForChildListener listener) {
mActionModeForChildListener = listener;
}
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
if (mActionModeForChildListener != null) {
return mActionModeForChildListener.startActionModeForChild(originalView, callback);
}
return super.startActionModeForChild(originalView, callback);
}
/**
* @hide
*/
public interface OnActionModeForChildListener {
ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback);
}
}

View file

@ -0,0 +1,282 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Movie;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.LongSparseArray;
import android.util.TypedValue;
import java.io.IOException;
import java.io.InputStream;
/**
* This extends Resources but delegates the calls to another Resources object. This enables
* any customization done by some subclass of Resources to be also picked up.
*/
class ResourcesWrapper extends Resources {
private final Resources mResources;
public ResourcesWrapper(Resources resources) {
super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
mResources = resources;
}
@Override
public CharSequence getText(int id) throws NotFoundException {
return mResources.getText(id);
}
@Override
public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
return mResources.getQuantityText(id, quantity);
}
@Override
public String getString(int id) throws NotFoundException {
return mResources.getString(id);
}
@Override
public String getString(int id, Object... formatArgs) throws NotFoundException {
return mResources.getString(id, formatArgs);
}
@Override
public String getQuantityString(int id, int quantity, Object... formatArgs)
throws NotFoundException {
return mResources.getQuantityString(id, quantity, formatArgs);
}
@Override
public String getQuantityString(int id, int quantity) throws NotFoundException {
return mResources.getQuantityString(id, quantity);
}
@Override
public CharSequence getText(int id, CharSequence def) {
return mResources.getText(id, def);
}
@Override
public CharSequence[] getTextArray(int id) throws NotFoundException {
return mResources.getTextArray(id);
}
@Override
public String[] getStringArray(int id) throws NotFoundException {
return mResources.getStringArray(id);
}
@Override
public int[] getIntArray(int id) throws NotFoundException {
return mResources.getIntArray(id);
}
@Override
public TypedArray obtainTypedArray(int id) throws NotFoundException {
return mResources.obtainTypedArray(id);
}
@Override
public float getDimension(int id) throws NotFoundException {
return mResources.getDimension(id);
}
@Override
public int getDimensionPixelOffset(int id) throws NotFoundException {
return mResources.getDimensionPixelOffset(id);
}
@Override
public int getDimensionPixelSize(int id) throws NotFoundException {
return mResources.getDimensionPixelSize(id);
}
@Override
public float getFraction(int id, int base, int pbase) {
return mResources.getFraction(id, base, pbase);
}
@Override
public Drawable getDrawable(int id) throws NotFoundException {
return mResources.getDrawable(id);
}
@Override
public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
return mResources.getDrawable(id, theme);
}
@Override
public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
return mResources.getDrawableForDensity(id, density);
}
@Override
public Drawable getDrawableForDensity(int id, int density, Theme theme) {
return mResources.getDrawableForDensity(id, density, theme);
}
@Override
public Movie getMovie(int id) throws NotFoundException {
return mResources.getMovie(id);
}
@Override
public int getColor(int id) throws NotFoundException {
return mResources.getColor(id);
}
@Override
public ColorStateList getColorStateList(int id) throws NotFoundException {
return mResources.getColorStateList(id);
}
@Override
public boolean getBoolean(int id) throws NotFoundException {
return mResources.getBoolean(id);
}
@Override
public int getInteger(int id) throws NotFoundException {
return mResources.getInteger(id);
}
@Override
public XmlResourceParser getLayout(int id) throws NotFoundException {
return mResources.getLayout(id);
}
@Override
public XmlResourceParser getAnimation(int id) throws NotFoundException {
return mResources.getAnimation(id);
}
@Override
public XmlResourceParser getXml(int id) throws NotFoundException {
return mResources.getXml(id);
}
@Override
public InputStream openRawResource(int id) throws NotFoundException {
return mResources.openRawResource(id);
}
@Override
public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
return mResources.openRawResource(id, value);
}
@Override
public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
return mResources.openRawResourceFd(id);
}
@Override
public void getValue(int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
mResources.getValue(id, outValue, resolveRefs);
}
@Override
public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
mResources.getValueForDensity(id, density, outValue, resolveRefs);
}
@Override
public void getValue(String name, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
mResources.getValue(name, outValue, resolveRefs);
}
@Override
public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
return mResources.obtainAttributes(set, attrs);
}
@Override
public void updateConfiguration(Configuration config, DisplayMetrics metrics) {
super.updateConfiguration(config, metrics);
if (mResources != null) { // called from super's constructor. So, need to check.
mResources.updateConfiguration(config, metrics);
}
}
@Override
public DisplayMetrics getDisplayMetrics() {
return mResources.getDisplayMetrics();
}
@Override
public Configuration getConfiguration() {
return mResources.getConfiguration();
}
@Override
public int getIdentifier(String name, String defType, String defPackage) {
return mResources.getIdentifier(name, defType, defPackage);
}
@Override
public String getResourceName(int resid) throws NotFoundException {
return mResources.getResourceName(resid);
}
@Override
public String getResourcePackageName(int resid) throws NotFoundException {
return mResources.getResourcePackageName(resid);
}
@Override
public String getResourceTypeName(int resid) throws NotFoundException {
return mResources.getResourceTypeName(resid);
}
@Override
public String getResourceEntryName(int resid) throws NotFoundException {
return mResources.getResourceEntryName(resid);
}
@Override
public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)
throws XmlPullParserException, IOException {
mResources.parseBundleExtras(parser, outBundle);
}
@Override
public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle)
throws XmlPullParserException {
mResources.parseBundleExtra(tagName, attrs, outBundle);
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
/**
* RtlSpacingHelper manages the relationship between left/right and start/end for views
* that need to maintain both absolute and relative settings for a form of spacing similar
* to view padding.
*
* @hide
*/
public class RtlSpacingHelper {
public static final int UNDEFINED = Integer.MIN_VALUE;
private int mLeft = 0;
private int mRight = 0;
private int mStart = UNDEFINED;
private int mEnd = UNDEFINED;
private int mExplicitLeft = 0;
private int mExplicitRight = 0;
private boolean mIsRtl = false;
private boolean mIsRelative = false;
public int getLeft() {
return mLeft;
}
public int getRight() {
return mRight;
}
public int getStart() {
return mIsRtl ? mRight : mLeft;
}
public int getEnd() {
return mIsRtl ? mLeft : mRight;
}
public void setRelative(int start, int end) {
mStart = start;
mEnd = end;
mIsRelative = true;
if (mIsRtl) {
if (end != UNDEFINED) mLeft = end;
if (start != UNDEFINED) mRight = start;
} else {
if (start != UNDEFINED) mLeft = start;
if (end != UNDEFINED) mRight = end;
}
}
public void setAbsolute(int left, int right) {
mIsRelative = false;
if (left != UNDEFINED) mLeft = mExplicitLeft = left;
if (right != UNDEFINED) mRight = mExplicitRight = right;
}
public void setDirection(boolean isRtl) {
if (isRtl == mIsRtl) {
return;
}
mIsRtl = isRtl;
if (mIsRelative) {
if (isRtl) {
mLeft = mEnd != UNDEFINED ? mEnd : mExplicitLeft;
mRight = mStart != UNDEFINED ? mStart : mExplicitRight;
} else {
mLeft = mStart != UNDEFINED ? mStart : mExplicitLeft;
mRight = mEnd != UNDEFINED ? mEnd : mExplicitRight;
}
} else {
mLeft = mExplicitLeft;
mRight = mExplicitRight;
}
}
}

View file

@ -0,0 +1,609 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.appcompat.R;
import android.support.v7.internal.view.ActionBarPolicy;
import android.support.v7.widget.LinearLayoutCompat;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.BaseAdapter;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
/**
* This widget implements the dynamic action bar tab behavior that can change across different
* configurations or circumstances.
*
* @hide
*/
public class ScrollingTabContainerView extends HorizontalScrollView
implements AdapterViewCompat.OnItemClickListener {
private static final String TAG = "ScrollingTabContainerView";
Runnable mTabSelector;
private TabClickListener mTabClickListener;
private LinearLayoutCompat mTabLayout;
private SpinnerCompat mTabSpinner;
private boolean mAllowCollapse;
int mMaxTabWidth;
int mStackedTabMaxWidth;
private int mContentHeight;
private int mSelectedTabIndex;
protected ViewPropertyAnimatorCompat mVisibilityAnim;
protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
private static final Interpolator sAlphaInterpolator = new DecelerateInterpolator();
private static final int FADE_DURATION = 200;
public ScrollingTabContainerView(Context context) {
super(context);
setHorizontalScrollBarEnabled(false);
ActionBarPolicy abp = ActionBarPolicy.get(context);
setContentHeight(abp.getTabContainerHeight());
mStackedTabMaxWidth = abp.getStackedTabMaxWidth();
mTabLayout = createTabLayout();
addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final boolean lockedExpanded = widthMode == MeasureSpec.EXACTLY;
setFillViewport(lockedExpanded);
final int childCount = mTabLayout.getChildCount();
if (childCount > 1 &&
(widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
if (childCount > 2) {
mMaxTabWidth = (int) (MeasureSpec.getSize(widthMeasureSpec) * 0.4f);
} else {
mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2;
}
mMaxTabWidth = Math.min(mMaxTabWidth, mStackedTabMaxWidth);
} else {
mMaxTabWidth = -1;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.EXACTLY);
final boolean canCollapse = !lockedExpanded && mAllowCollapse;
if (canCollapse) {
// See if we should expand
mTabLayout.measure(MeasureSpec.UNSPECIFIED, heightMeasureSpec);
if (mTabLayout.getMeasuredWidth() > MeasureSpec.getSize(widthMeasureSpec)) {
performCollapse();
} else {
performExpand();
}
} else {
performExpand();
}
final int oldWidth = getMeasuredWidth();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int newWidth = getMeasuredWidth();
if (lockedExpanded && oldWidth != newWidth) {
// Recenter the tab display if we're at a new (scrollable) size.
setTabSelected(mSelectedTabIndex);
}
}
/**
* Indicates whether this view is collapsed into a dropdown menu instead
* of traditional tabs.
* @return true if showing as a spinner
*/
private boolean isCollapsed() {
return mTabSpinner != null && mTabSpinner.getParent() == this;
}
public void setAllowCollapse(boolean allowCollapse) {
mAllowCollapse = allowCollapse;
}
private void performCollapse() {
if (isCollapsed()) return;
if (mTabSpinner == null) {
mTabSpinner = createSpinner();
}
removeView(mTabLayout);
addView(mTabSpinner, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT));
if (mTabSpinner.getAdapter() == null) {
mTabSpinner.setAdapter(new TabAdapter());
}
if (mTabSelector != null) {
removeCallbacks(mTabSelector);
mTabSelector = null;
}
mTabSpinner.setSelection(mSelectedTabIndex);
}
private boolean performExpand() {
if (!isCollapsed()) return false;
removeView(mTabSpinner);
addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT));
setTabSelected(mTabSpinner.getSelectedItemPosition());
return false;
}
public void setTabSelected(int position) {
mSelectedTabIndex = position;
final int tabCount = mTabLayout.getChildCount();
for (int i = 0; i < tabCount; i++) {
final View child = mTabLayout.getChildAt(i);
final boolean isSelected = i == position;
child.setSelected(isSelected);
if (isSelected) {
animateToTab(position);
}
}
if (mTabSpinner != null && position >= 0) {
mTabSpinner.setSelection(position);
}
}
public void setContentHeight(int contentHeight) {
mContentHeight = contentHeight;
requestLayout();
}
private LinearLayoutCompat createTabLayout() {
final LinearLayoutCompat tabLayout = new LinearLayoutCompat(getContext(), null,
R.attr.actionBarTabBarStyle);
tabLayout.setMeasureWithLargestChildEnabled(true);
tabLayout.setGravity(Gravity.CENTER);
tabLayout.setLayoutParams(new LinearLayoutCompat.LayoutParams(
LinearLayoutCompat.LayoutParams.WRAP_CONTENT, LinearLayoutCompat.LayoutParams.MATCH_PARENT));
return tabLayout;
}
private SpinnerCompat createSpinner() {
final SpinnerCompat spinner = new SpinnerCompat(getContext(), null,
R.attr.actionDropDownStyle);
spinner.setLayoutParams(new LinearLayoutCompat.LayoutParams(
LinearLayoutCompat.LayoutParams.WRAP_CONTENT, LinearLayoutCompat.LayoutParams.MATCH_PARENT));
spinner.setOnItemClickListenerInt(this);
return spinner;
}
protected void onConfigurationChanged(Configuration newConfig) {
if (Build.VERSION.SDK_INT >= 8) {
super.onConfigurationChanged(newConfig);
}
ActionBarPolicy abp = ActionBarPolicy.get(getContext());
// Action bar can change size on configuration changes.
// Reread the desired height from the theme-specified style.
setContentHeight(abp.getTabContainerHeight());
mStackedTabMaxWidth = abp.getStackedTabMaxWidth();
}
public void animateToVisibility(int visibility) {
if (mVisibilityAnim != null) {
mVisibilityAnim.cancel();
}
if (visibility == VISIBLE) {
if (getVisibility() != VISIBLE) {
ViewCompat.setAlpha(this, 0f);
}
ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(1f);
anim.setDuration(FADE_DURATION);
anim.setInterpolator(sAlphaInterpolator);
anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
anim.start();
} else {
ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(0f);
anim.setDuration(FADE_DURATION);
anim.setInterpolator(sAlphaInterpolator);
anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
anim.start();
}
}
public void animateToTab(final int position) {
final View tabView = mTabLayout.getChildAt(position);
if (mTabSelector != null) {
removeCallbacks(mTabSelector);
}
mTabSelector = new Runnable() {
public void run() {
final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;
smoothScrollTo(scrollPos, 0);
mTabSelector = null;
}
};
post(mTabSelector);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
if (mTabSelector != null) {
// Re-post the selector we saved
post(mTabSelector);
}
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mTabSelector != null) {
removeCallbacks(mTabSelector);
}
}
private TabView createTabView(ActionBar.Tab tab, boolean forAdapter) {
final TabView tabView = new TabView(getContext(), tab, forAdapter);
if (forAdapter) {
tabView.setBackgroundDrawable(null);
tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT,
mContentHeight));
} else {
tabView.setFocusable(true);
if (mTabClickListener == null) {
mTabClickListener = new TabClickListener();
}
tabView.setOnClickListener(mTabClickListener);
}
return tabView;
}
public void addTab(ActionBar.Tab tab, boolean setSelected) {
TabView tabView = createTabView(tab, false);
mTabLayout.addView(tabView, new LinearLayoutCompat.LayoutParams(0,
LayoutParams.MATCH_PARENT, 1));
if (mTabSpinner != null) {
((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (setSelected) {
tabView.setSelected(true);
}
if (mAllowCollapse) {
requestLayout();
}
}
public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
final TabView tabView = createTabView(tab, false);
mTabLayout.addView(tabView, position, new LinearLayoutCompat.LayoutParams(
0, LayoutParams.MATCH_PARENT, 1));
if (mTabSpinner != null) {
((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (setSelected) {
tabView.setSelected(true);
}
if (mAllowCollapse) {
requestLayout();
}
}
public void updateTab(int position) {
((TabView) mTabLayout.getChildAt(position)).update();
if (mTabSpinner != null) {
((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (mAllowCollapse) {
requestLayout();
}
}
public void removeTabAt(int position) {
mTabLayout.removeViewAt(position);
if (mTabSpinner != null) {
((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (mAllowCollapse) {
requestLayout();
}
}
public void removeAllTabs() {
mTabLayout.removeAllViews();
if (mTabSpinner != null) {
((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (mAllowCollapse) {
requestLayout();
}
}
@Override
public void onItemClick(AdapterViewCompat<?> parent, View view, int position, long id) {
TabView tabView = (TabView) view;
tabView.getTab().select();
}
private class TabView extends LinearLayoutCompat implements OnLongClickListener {
private final int[] BG_ATTRS = {
android.R.attr.background
};
private ActionBar.Tab mTab;
private TextView mTextView;
private ImageView mIconView;
private View mCustomView;
public TabView(Context context, ActionBar.Tab tab, boolean forList) {
super(context, null, R.attr.actionBarTabStyle);
mTab = tab;
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, null, BG_ATTRS,
R.attr.actionBarTabStyle, 0);
if (a.hasValue(0)) {
setBackgroundDrawable(a.getDrawable(0));
}
a.recycle();
if (forList) {
setGravity(GravityCompat.START | Gravity.CENTER_VERTICAL);
}
update();
}
public void bindTab(ActionBar.Tab tab) {
mTab = tab;
update();
}
@Override
public void setSelected(boolean selected) {
final boolean changed = (isSelected() != selected);
super.setSelected(selected);
if (changed && selected) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
// This view masquerades as an action bar tab.
event.setClassName(ActionBar.Tab.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (Build.VERSION.SDK_INT >= 14) {
// This view masquerades as an action bar tab.
info.setClassName(ActionBar.Tab.class.getName());
}
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Re-measure if we went beyond our maximum size.
if (mMaxTabWidth > 0 && getMeasuredWidth() > mMaxTabWidth) {
super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxTabWidth, MeasureSpec.EXACTLY),
heightMeasureSpec);
}
}
public void update() {
final ActionBar.Tab tab = mTab;
final View custom = tab.getCustomView();
if (custom != null) {
final ViewParent customParent = custom.getParent();
if (customParent != this) {
if (customParent != null) ((ViewGroup) customParent).removeView(custom);
addView(custom);
}
mCustomView = custom;
if (mTextView != null) mTextView.setVisibility(GONE);
if (mIconView != null) {
mIconView.setVisibility(GONE);
mIconView.setImageDrawable(null);
}
} else {
if (mCustomView != null) {
removeView(mCustomView);
mCustomView = null;
}
final Drawable icon = tab.getIcon();
final CharSequence text = tab.getText();
if (icon != null) {
if (mIconView == null) {
ImageView iconView = new ImageView(getContext());
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_VERTICAL;
iconView.setLayoutParams(lp);
addView(iconView, 0);
mIconView = iconView;
}
mIconView.setImageDrawable(icon);
mIconView.setVisibility(VISIBLE);
} else if (mIconView != null) {
mIconView.setVisibility(GONE);
mIconView.setImageDrawable(null);
}
final boolean hasText = !TextUtils.isEmpty(text);
if (hasText) {
if (mTextView == null) {
TextView textView = new CompatTextView(getContext(), null,
R.attr.actionBarTabTextStyle);
textView.setEllipsize(TruncateAt.END);
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_VERTICAL;
textView.setLayoutParams(lp);
addView(textView);
mTextView = textView;
}
mTextView.setText(text);
mTextView.setVisibility(VISIBLE);
} else if (mTextView != null) {
mTextView.setVisibility(GONE);
mTextView.setText(null);
}
if (mIconView != null) {
mIconView.setContentDescription(tab.getContentDescription());
}
if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
setOnLongClickListener(this);
} else {
setOnLongClickListener(null);
setLongClickable(false);
}
}
}
public boolean onLongClick(View v) {
final int[] screenPos = new int[2];
getLocationOnScreen(screenPos);
final Context context = getContext();
final int width = getWidth();
final int height = getHeight();
final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
Toast.LENGTH_SHORT);
// Show under the tab
cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
(screenPos[0] + width / 2) - screenWidth / 2, height);
cheatSheet.show();
return true;
}
public ActionBar.Tab getTab() {
return mTab;
}
}
private class TabAdapter extends BaseAdapter {
@Override
public int getCount() {
return mTabLayout.getChildCount();
}
@Override
public Object getItem(int position) {
return ((TabView) mTabLayout.getChildAt(position)).getTab();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = createTabView((ActionBar.Tab) getItem(position), true);
} else {
((TabView) convertView).bindTab((ActionBar.Tab) getItem(position));
}
return convertView;
}
}
private class TabClickListener implements OnClickListener {
public void onClick(View view) {
TabView tabView = (TabView) view;
tabView.getTab().select();
final int tabCount = mTabLayout.getChildCount();
for (int i = 0; i < tabCount; i++) {
final View child = mTabLayout.getChildAt(i);
child.setSelected(child == view);
}
}
}
protected class VisibilityAnimListener implements ViewPropertyAnimatorListener {
private boolean mCanceled = false;
private int mFinalVisibility;
public VisibilityAnimListener withFinalVisibility(ViewPropertyAnimatorCompat animation,
int visibility) {
mFinalVisibility = visibility;
mVisibilityAnim = animation;
return this;
}
@Override
public void onAnimationStart(View view) {
setVisibility(VISIBLE);
mCanceled = false;
}
@Override
public void onAnimationEnd(View view) {
if (mCanceled) return;
mVisibilityAnim = null;
setVisibility(mFinalVisibility);
}
@Override
public void onAnimationCancel(View view) {
mCanceled = true;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,107 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.v4.graphics.ColorUtils;
import android.util.TypedValue;
class ThemeUtils {
private static final ThreadLocal<TypedValue> TL_TYPED_VALUE = new ThreadLocal<>();
private static final int[] DISABLED_STATE_SET = new int[]{-android.R.attr.state_enabled};
private static final int[] EMPTY_STATE_SET = new int[0];
private static final int[] TEMP_ARRAY = new int[1];
static ColorStateList createDisabledStateList(int textColor, int disabledTextColor) {
// Now create a new ColorStateList with the default color, and the new disabled
// color
final int[][] states = new int[2][];
final int[] colors = new int[2];
int i = 0;
// Disabled state
states[i] = DISABLED_STATE_SET;
colors[i] = disabledTextColor;
i++;
// Default state
states[i] = EMPTY_STATE_SET;
colors[i] = textColor;
i++;
return new ColorStateList(states, colors);
}
static int getThemeAttrColor(Context context, int attr) {
TEMP_ARRAY[0] = attr;
TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
try {
return a.getColor(0, 0);
} finally {
a.recycle();
}
}
static ColorStateList getThemeAttrColorStateList(Context context, int attr) {
TEMP_ARRAY[0] = attr;
TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
try {
return a.getColorStateList(0);
} finally {
a.recycle();
}
}
static int getDisabledThemeAttrColor(Context context, int attr) {
final ColorStateList csl = getThemeAttrColorStateList(context, attr);
if (csl != null && csl.isStateful()) {
// If the CSL is stateful, we'll assume it has a disabled state and use it
return csl.getColorForState(DISABLED_STATE_SET, csl.getDefaultColor());
} else {
// Else, we'll generate the color using disabledAlpha from the theme
final TypedValue tv = getTypedValue();
// Now retrieve the disabledAlpha value from the theme
context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true);
final float disabledAlpha = tv.getFloat();
return getThemeAttrColor(context, attr, disabledAlpha);
}
}
private static TypedValue getTypedValue() {
TypedValue typedValue = TL_TYPED_VALUE.get();
if (typedValue == null) {
typedValue = new TypedValue();
TL_TYPED_VALUE.set(typedValue);
}
return typedValue;
}
static int getThemeAttrColor(Context context, int attr, float alpha) {
final int color = getThemeAttrColor(context, attr);
final int originalAlpha = Color.alpha(color);
return ColorUtils.setAlphaComponent(color, Math.round(originalAlpha * alpha));
}
}

View file

@ -0,0 +1,142 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.support.annotation.Nullable;
import android.support.v4.view.TintableBackgroundView;
import android.util.AttributeSet;
import android.widget.AutoCompleteTextView;
/**
* An tint aware {@link android.widget.AutoCompleteTextView}.
* <p>
* This will automatically be used when you use {@link AutoCompleteTextView} in your layouts. You
* should only need to manually use this class writing custom views.
*/
public class TintAutoCompleteTextView extends AutoCompleteTextView implements
TintableBackgroundView {
private static final int[] TINT_ATTRS = {
android.R.attr.background,
android.R.attr.popupBackground
};
private TintManager mTintManager;
private TintInfo mBackgroundTint;
public TintAutoCompleteTextView(Context context) {
this(context, null);
}
public TintAutoCompleteTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.autoCompleteTextViewStyle);
}
public TintAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
if (TintManager.SHOULD_BE_USED) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
TINT_ATTRS, defStyleAttr, 0);
mTintManager = a.getTintManager();
if (a.hasValue(0)) {
setSupportBackgroundTintList(
mTintManager.getColorStateList(a.getResourceId(0, -1)));
}
if (a.hasValue(1)) {
setDropDownBackgroundDrawable(a.getDrawable(1));
}
a.recycle();
}
}
@Override
public void setDropDownBackgroundResource(int id) {
setDropDownBackgroundDrawable(mTintManager.getDrawable(id));
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View,
* android.content.res.ColorStateList)}
*
* @hide
*/
@Override
public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
if (mBackgroundTint == null) {
mBackgroundTint = new TintInfo();
}
mBackgroundTint.mTintList = tint;
applySupportBackgroundTint();
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)}
*
* @hide
*/
@Override
@Nullable
public ColorStateList getSupportBackgroundTintList() {
return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)}
*
* @hide
*/
@Override
public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
if (mBackgroundTint == null) {
mBackgroundTint = new TintInfo();
}
mBackgroundTint.mTintMode = tintMode;
applySupportBackgroundTint();
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)}
*
* @hide
*/
@Override
@Nullable
public PorterDuff.Mode getSupportBackgroundTintMode() {
return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
applySupportBackgroundTint();
}
private void applySupportBackgroundTint() {
if (getBackground() != null && mBackgroundTint != null) {
TintManager.tintViewBackground(this, mBackgroundTint);
}
}
}

View file

@ -0,0 +1,154 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v4.view.TintableBackgroundView;
import android.util.AttributeSet;
import android.widget.Button;
/**
* An tint aware {@link android.widget.Button}.
* <p>
* This will automatically be used when you use {@link android.widget.Button} in your layouts. You
* should only need to manually use this class when writing custom views.
*/
public class TintButton extends Button implements TintableBackgroundView {
private static final int[] TINT_ATTRS = {
android.R.attr.background
};
private TintInfo mBackgroundTint;
public TintButton(Context context) {
this(context, null);
}
public TintButton(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.buttonStyle);
}
public TintButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (TintManager.SHOULD_BE_USED) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
TINT_ATTRS, defStyleAttr, 0);
if (a.hasValue(0)) {
setSupportBackgroundTintList(
a.getTintManager().getColorStateList(a.getResourceId(0, -1)));
}
a.recycle();
}
final ColorStateList textColors = getTextColors();
if (textColors != null && !textColors.isStateful()) {
// If we have a ColorStateList which isn't stateful, create one which includes
// a disabled state
final int disabledTextColor;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// Pre-Lollipop, we will use textColorSecondary with android:disabledAlpha
// applied
disabledTextColor = ThemeUtils.getDisabledThemeAttrColor(context,
android.R.attr.textColorSecondary);
} else {
// With certain styles on Lollipop, there is a StateListAnimator which sets
// an alpha on the whole view, so we don't need to apply disabledAlpha to
// textColorSecondary
disabledTextColor = ThemeUtils.getThemeAttrColor(context,
android.R.attr.textColorSecondary);
}
setTextColor(ThemeUtils.createDisabledStateList(
textColors.getDefaultColor(), disabledTextColor));
}
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View,
* android.content.res.ColorStateList)}
*
* @hide
*/
@Override
public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
if (mBackgroundTint == null) {
mBackgroundTint = new TintInfo();
}
mBackgroundTint.mTintList = tint;
applySupportBackgroundTint();
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)}
*
* @hide
*/
@Override
@Nullable
public ColorStateList getSupportBackgroundTintList() {
return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)}
*
* @hide
*/
@Override
public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
if (mBackgroundTint == null) {
mBackgroundTint = new TintInfo();
}
mBackgroundTint.mTintMode = tintMode;
applySupportBackgroundTint();
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)}
*
* @hide
*/
@Override
@Nullable
public PorterDuff.Mode getSupportBackgroundTintMode() {
return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
applySupportBackgroundTint();
}
private void applySupportBackgroundTint() {
if (getBackground() != null && mBackgroundTint != null) {
TintManager.tintViewBackground(this, mBackgroundTint);
}
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.CheckBox;
/**
* An tint aware {@link android.widget.CheckBox}.
* <p>
* This will automatically be used when you use {@link android.widget.CheckBox} in your layouts.
* You should only need to manually use this class when writing custom views.
*/
public class TintCheckBox extends CheckBox {
private static final int[] TINT_ATTRS = {
android.R.attr.button
};
private TintManager mTintManager;
public TintCheckBox(Context context) {
this(context, null);
}
public TintCheckBox(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.checkboxStyle);
}
public TintCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (TintManager.SHOULD_BE_USED) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
TINT_ATTRS, defStyleAttr, 0);
setButtonDrawable(a.getDrawable(0));
a.recycle();
mTintManager = a.getTintManager();
}
}
@Override
public void setButtonDrawable(int resid) {
if (mTintManager != null) {
setButtonDrawable(mTintManager.getDrawable(resid));
} else {
super.setButtonDrawable(resid);
}
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.CheckedTextView;
/**
* An tint aware {@link android.widget.CheckedTextView}.
* <p>
* This will automatically be used when you use {@link android.widget.CheckedTextView} in your
* layouts. You should only need to manually use this class when writing custom views.
*/
public class TintCheckedTextView extends CheckedTextView {
private static final int[] TINT_ATTRS = {
android.R.attr.checkMark
};
private TintManager mTintManager;
public TintCheckedTextView(Context context) {
this(context, null);
}
public TintCheckedTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.checkedTextViewStyle);
}
public TintCheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (TintManager.SHOULD_BE_USED) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
TINT_ATTRS, defStyleAttr, 0);
setCheckMarkDrawable(a.getDrawable(0));
a.recycle();
mTintManager = a.getTintManager();
}
}
@Override
public void setCheckMarkDrawable(int resid) {
if (mTintManager != null) {
setCheckMarkDrawable(mTintManager.getDrawable(resid));
} else {
super.setCheckMarkDrawable(resid);
}
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
/**
* A {@link android.content.ContextWrapper} which returns a tint-aware
* {@link android.content.res.Resources} instance from {@link #getResources()}.
*
* @hide
*/
class TintContextWrapper extends ContextWrapper {
private final TintManager mTintManager;
public static Context wrap(Context context) {
if (!(context instanceof TintContextWrapper)) {
context = new TintContextWrapper(context);
}
return context;
}
TintContextWrapper(Context base) {
super(base);
mTintManager = new TintManager(base);
}
@Override
public Resources getResources() {
return mTintManager.getResources();
}
final TintManager getTintManager() {
return mTintManager;
}
}

View file

@ -0,0 +1,129 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.support.annotation.Nullable;
import android.support.v4.view.TintableBackgroundView;
import android.util.AttributeSet;
import android.widget.EditText;
/**
* An tint aware {@link android.widget.EditText}.
* <p>
* This will automatically be used when you use {@link android.widget.EditText} in your
* layouts. You should only need to manually use this class when writing custom views.
*/
public class TintEditText extends EditText implements TintableBackgroundView {
private static final int[] TINT_ATTRS = {
android.R.attr.background
};
private TintInfo mBackgroundTint;
public TintEditText(Context context) {
this(context, null);
}
public TintEditText(Context context, AttributeSet attrs) {
this(TintContextWrapper.wrap(context), attrs, android.R.attr.editTextStyle);
}
public TintEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (TintManager.SHOULD_BE_USED) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
TINT_ATTRS, defStyleAttr, 0);
if (a.hasValue(0)) {
setSupportBackgroundTintList(
a.getTintManager().getColorStateList(a.getResourceId(0, -1)));
}
a.recycle();
}
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View,
* android.content.res.ColorStateList)}
*
* @hide
*/
@Override
public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
if (mBackgroundTint == null) {
mBackgroundTint = new TintInfo();
}
mBackgroundTint.mTintList = tint;
applySupportBackgroundTint();
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)}
*
* @hide
*/
@Override
@Nullable
public ColorStateList getSupportBackgroundTintList() {
return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)}
*
* @hide
*/
@Override
public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
if (mBackgroundTint == null) {
mBackgroundTint = new TintInfo();
}
mBackgroundTint.mTintMode = tintMode;
applySupportBackgroundTint();
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)}
*
* @hide
*/
@Override
@Nullable
public PorterDuff.Mode getSupportBackgroundTintMode() {
return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
applySupportBackgroundTint();
}
private void applySupportBackgroundTint() {
if (getBackground() != null && mBackgroundTint != null) {
TintManager.tintViewBackground(this, mBackgroundTint);
}
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.support.annotation.DrawableRes;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* An tint aware {@link android.widget.ImageView}
*
* @hide
*/
public class TintImageView extends ImageView {
private static final int[] TINT_ATTRS = {
android.R.attr.background,
android.R.attr.src
};
private final TintManager mTintManager;
public TintImageView(Context context) {
this(context, null);
}
public TintImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TintImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, TINT_ATTRS,
defStyleAttr, 0);
if (a.length() > 0) {
if (a.hasValue(0)) {
setBackgroundDrawable(a.getDrawable(0));
}
if (a.hasValue(1)) {
setImageDrawable(a.getDrawable(1));
}
}
a.recycle();
// Keep the TintManager in case we need it later
mTintManager = a.getTintManager();
}
@Override
public void setImageResource(@DrawableRes int resId) {
// Intercept this call and instead retrieve the Drawable via the tint manager
setImageDrawable(mTintManager.getDrawable(resId));
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
class TintInfo {
ColorStateList mTintList;
PorterDuff.Mode mTintMode;
}

View file

@ -0,0 +1,511 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.util.LruCache;
import android.support.v7.appcompat.R;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.View;
import static android.support.v7.internal.widget.ThemeUtils.getDisabledThemeAttrColor;
import static android.support.v7.internal.widget.ThemeUtils.getThemeAttrColor;
import static android.support.v7.internal.widget.ThemeUtils.getThemeAttrColorStateList;
/**
* @hide
*/
public final class TintManager {
static final boolean SHOULD_BE_USED = Build.VERSION.SDK_INT < 21;
private static final String TAG = TintManager.class.getSimpleName();
private static final boolean DEBUG = false;
static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN;
private static final ColorFilterLruCache COLOR_FILTER_CACHE = new ColorFilterLruCache(6);
/**
* Drawables which should be tinted with the value of {@code R.attr.colorControlNormal},
* using the default mode.
*/
private static final int[] TINT_COLOR_CONTROL_NORMAL = {
R.drawable.abc_ic_ab_back_mtrl_am_alpha,
R.drawable.abc_ic_go_search_api_mtrl_alpha,
R.drawable.abc_ic_search_api_mtrl_alpha,
R.drawable.abc_ic_commit_search_api_mtrl_alpha,
R.drawable.abc_ic_clear_mtrl_alpha,
R.drawable.abc_ic_menu_share_mtrl_alpha,
R.drawable.abc_ic_menu_copy_mtrl_am_alpha,
R.drawable.abc_ic_menu_cut_mtrl_alpha,
R.drawable.abc_ic_menu_selectall_mtrl_alpha,
R.drawable.abc_ic_menu_paste_mtrl_am_alpha,
R.drawable.abc_ic_menu_moreoverflow_mtrl_alpha,
R.drawable.abc_ic_voice_search_api_mtrl_alpha,
R.drawable.abc_textfield_search_default_mtrl_alpha,
R.drawable.abc_textfield_default_mtrl_alpha,
R.drawable.abc_ab_share_pack_mtrl_alpha
};
/**
* Drawables which should be tinted with the value of {@code R.attr.colorControlActivated},
* using the default mode.
*/
private static final int[] TINT_COLOR_CONTROL_ACTIVATED = {
R.drawable.abc_textfield_activated_mtrl_alpha,
R.drawable.abc_textfield_search_activated_mtrl_alpha,
R.drawable.abc_cab_background_top_mtrl_alpha,
R.drawable.abc_text_cursor_mtrl_alpha
};
/**
* Drawables which should be tinted with the value of {@code android.R.attr.colorBackground},
* using the {@link android.graphics.PorterDuff.Mode#MULTIPLY} mode.
*/
private static final int[] TINT_COLOR_BACKGROUND_MULTIPLY = {
R.drawable.abc_popup_background_mtrl_mult,
R.drawable.abc_cab_background_internal_bg,
R.drawable.abc_menu_hardkey_panel_mtrl_mult
};
/**
* Drawables which should be tinted using a state list containing values of
* {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated}
*/
private static final int[] TINT_COLOR_CONTROL_STATE_LIST = {
R.drawable.abc_edit_text_material,
R.drawable.abc_tab_indicator_material,
R.drawable.abc_textfield_search_material,
R.drawable.abc_spinner_mtrl_am_alpha,
R.drawable.abc_btn_check_material,
R.drawable.abc_btn_radio_material,
R.drawable.abc_spinner_textfield_background_material,
R.drawable.abc_ratingbar_full_material,
R.drawable.abc_switch_track_mtrl_alpha,
R.drawable.abc_switch_thumb_material,
R.drawable.abc_btn_default_mtrl_shape,
R.drawable.abc_btn_borderless_material
};
/**
* Drawables which contain other drawables which should be tinted. The child drawable IDs
* should be defined in one of the arrays above.
*/
private static final int[] CONTAINERS_WITH_TINT_CHILDREN = {
R.drawable.abc_cab_background_top_material
};
private final Context mContext;
private final Resources mResources;
private final TypedValue mTypedValue;
private final SparseArray<ColorStateList> mColorStateLists;
private ColorStateList mDefaultColorStateList;
/**
* A helper method to instantiate a {@link TintManager} and then call {@link #getDrawable(int)}.
* This method should not be used routinely.
*/
public static Drawable getDrawable(Context context, int resId) {
if (isInTintList(resId)) {
final TintManager tm = (context instanceof TintContextWrapper)
? ((TintContextWrapper) context).getTintManager()
: new TintManager(context);
return tm.getDrawable(resId);
} else {
return ContextCompat.getDrawable(context, resId);
}
}
public TintManager(Context context) {
mColorStateLists = new SparseArray<>();
mContext = context;
mTypedValue = new TypedValue();
mResources = new TintResources(context.getResources(), this);
}
Resources getResources() {
return mResources;
}
public Drawable getDrawable(int resId) {
Drawable drawable = ContextCompat.getDrawable(mContext, resId);
if (drawable != null) {
drawable = drawable.mutate();
if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) {
ColorStateList colorStateList = getColorStateListForKnownDrawableId(resId);
PorterDuff.Mode tintMode = DEFAULT_MODE;
if (resId == R.drawable.abc_switch_thumb_material) {
tintMode = PorterDuff.Mode.MULTIPLY;
}
if (colorStateList != null) {
drawable = DrawableCompat.wrap(drawable);
DrawableCompat.setTintList(drawable, colorStateList);
DrawableCompat.setTintMode(drawable, tintMode);
}
} else if (arrayContains(CONTAINERS_WITH_TINT_CHILDREN, resId)) {
drawable = mResources.getDrawable(resId);
} else {
tintDrawable(resId, drawable);
}
}
return drawable;
}
void tintDrawable(final int resId, final Drawable drawable) {
PorterDuff.Mode tintMode = null;
boolean colorAttrSet = false;
int colorAttr = 0;
int alpha = -1;
if (arrayContains(TINT_COLOR_CONTROL_NORMAL, resId)) {
colorAttr = R.attr.colorControlNormal;
colorAttrSet = true;
} else if (arrayContains(TINT_COLOR_CONTROL_ACTIVATED, resId)) {
colorAttr = R.attr.colorControlActivated;
colorAttrSet = true;
} else if (arrayContains(TINT_COLOR_BACKGROUND_MULTIPLY, resId)) {
colorAttr = android.R.attr.colorBackground;
colorAttrSet = true;
tintMode = PorterDuff.Mode.MULTIPLY;
} else if (resId == R.drawable.abc_list_divider_mtrl_alpha) {
colorAttr = android.R.attr.colorForeground;
colorAttrSet = true;
alpha = Math.round(0.16f * 255);
}
if (colorAttrSet) {
if (tintMode == null) {
tintMode = DEFAULT_MODE;
}
final int color = getThemeAttrColor(mContext, colorAttr);
tintDrawableUsingColorFilter(drawable, color, tintMode);
if (alpha != -1) {
drawable.setAlpha(alpha);
}
if (DEBUG) {
Log.d(TAG, "Tinted Drawable ID: " + mResources.getResourceName(resId) +
" with color: #" + Integer.toHexString(color));
}
}
}
private static boolean arrayContains(int[] array, int value) {
for (int id : array) {
if (id == value) {
return true;
}
}
return false;
}
private static boolean isInTintList(int drawableId) {
return arrayContains(TINT_COLOR_BACKGROUND_MULTIPLY, drawableId) ||
arrayContains(TINT_COLOR_CONTROL_NORMAL, drawableId) ||
arrayContains(TINT_COLOR_CONTROL_ACTIVATED, drawableId) ||
arrayContains(TINT_COLOR_CONTROL_STATE_LIST, drawableId) ||
arrayContains(CONTAINERS_WITH_TINT_CHILDREN, drawableId);
}
ColorStateList getColorStateList(int resId) {
return arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)
? getColorStateListForKnownDrawableId(resId)
: null;
}
private ColorStateList getColorStateListForKnownDrawableId(int resId) {
// Try the cache first
ColorStateList colorStateList = mColorStateLists.get(resId);
if (colorStateList == null) {
// ...if the cache did not contain a color state list, try and create
if (resId == R.drawable.abc_edit_text_material) {
colorStateList = createEditTextColorStateList();
} else if (resId == R.drawable.abc_switch_track_mtrl_alpha) {
colorStateList = createSwitchTrackColorStateList();
} else if (resId == R.drawable.abc_switch_thumb_material) {
colorStateList = createSwitchThumbColorStateList();
} else if (resId == R.drawable.abc_btn_default_mtrl_shape
|| resId == R.drawable.abc_btn_borderless_material) {
colorStateList = createButtonColorStateList();
} else if (resId == R.drawable.abc_spinner_mtrl_am_alpha
|| resId == R.drawable.abc_spinner_textfield_background_material) {
colorStateList = createSpinnerColorStateList();
} else {
// If we don't have an explicit color state list for this Drawable, use the default
colorStateList = getDefaultColorStateList();
}
// ..and add it to the cache
mColorStateLists.append(resId, colorStateList);
}
return colorStateList;
}
private ColorStateList getDefaultColorStateList() {
if (mDefaultColorStateList == null) {
/**
* Generate the default color state list which uses the colorControl attributes.
* Order is important here. The default enabled state needs to go at the bottom.
*/
final int colorControlNormal = getThemeAttrColor(mContext, R.attr.colorControlNormal);
final int colorControlActivated = getThemeAttrColor(mContext,
R.attr.colorControlActivated);
final int[][] states = new int[7][];
final int[] colors = new int[7];
int i = 0;
// Disabled state
states[i] = new int[] { -android.R.attr.state_enabled };
colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorControlNormal);
i++;
states[i] = new int[] { android.R.attr.state_focused };
colors[i] = colorControlActivated;
i++;
states[i] = new int[] { android.R.attr.state_activated };
colors[i] = colorControlActivated;
i++;
states[i] = new int[] { android.R.attr.state_pressed };
colors[i] = colorControlActivated;
i++;
states[i] = new int[] { android.R.attr.state_checked };
colors[i] = colorControlActivated;
i++;
states[i] = new int[] { android.R.attr.state_selected };
colors[i] = colorControlActivated;
i++;
// Default enabled state
states[i] = new int[0];
colors[i] = colorControlNormal;
i++;
mDefaultColorStateList = new ColorStateList(states, colors);
}
return mDefaultColorStateList;
}
private ColorStateList createSwitchTrackColorStateList() {
final int[][] states = new int[3][];
final int[] colors = new int[3];
int i = 0;
// Disabled state
states[i] = new int[]{-android.R.attr.state_enabled};
colors[i] = getThemeAttrColor(mContext, android.R.attr.colorForeground, 0.1f);
i++;
states[i] = new int[]{android.R.attr.state_checked};
colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated, 0.3f);
i++;
// Default enabled state
states[i] = new int[0];
colors[i] = getThemeAttrColor(mContext, android.R.attr.colorForeground, 0.3f);
i++;
return new ColorStateList(states, colors);
}
private ColorStateList createSwitchThumbColorStateList() {
final int[][] states = new int[3][];
final int[] colors = new int[3];
int i = 0;
final ColorStateList thumbColor = getThemeAttrColorStateList(mContext,
R.attr.colorSwitchThumbNormal);
if (thumbColor != null && thumbColor.isStateful()) {
// If colorSwitchThumbNormal is a valid ColorStateList, extract the default and
// disabled colors from it
// Disabled state
states[i] = new int[]{-android.R.attr.state_enabled};
colors[i] = thumbColor.getColorForState(states[i], 0);
i++;
states[i] = new int[]{android.R.attr.state_checked};
colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated);
i++;
// Default enabled state
states[i] = new int[0];
colors[i] = thumbColor.getDefaultColor();
i++;
} else {
// Else we'll use an approximation using the default disabled alpha
// Disabled state
states[i] = new int[]{-android.R.attr.state_enabled};
colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorSwitchThumbNormal);
i++;
states[i] = new int[]{android.R.attr.state_checked};
colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated);
i++;
// Default enabled state
states[i] = new int[0];
colors[i] = getThemeAttrColor(mContext, R.attr.colorSwitchThumbNormal);
i++;
}
return new ColorStateList(states, colors);
}
private ColorStateList createEditTextColorStateList() {
final int[][] states = new int[3][];
final int[] colors = new int[3];
int i = 0;
// Disabled state
states[i] = new int[]{-android.R.attr.state_enabled};
colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorControlNormal);
i++;
states[i] = new int[]{-android.R.attr.state_pressed, -android.R.attr.state_focused};
colors[i] = getThemeAttrColor(mContext, R.attr.colorControlNormal);
i++;
// Default enabled state
states[i] = new int[0];
colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated);
i++;
return new ColorStateList(states, colors);
}
private ColorStateList createButtonColorStateList() {
final int[][] states = new int[4][];
final int[] colors = new int[4];
int i = 0;
// Disabled state
states[i] = new int[]{-android.R.attr.state_enabled};
colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorButtonNormal);
i++;
states[i] = new int[]{android.R.attr.state_pressed};
colors[i] = getThemeAttrColor(mContext, R.attr.colorControlHighlight);
i++;
states[i] = new int[]{android.R.attr.state_focused};
colors[i] = getThemeAttrColor(mContext, R.attr.colorControlHighlight);
i++;
// Default enabled state
states[i] = new int[0];
colors[i] = getThemeAttrColor(mContext, R.attr.colorButtonNormal);
i++;
return new ColorStateList(states, colors);
}
private ColorStateList createSpinnerColorStateList() {
final int[][] states = new int[3][];
final int[] colors = new int[3];
int i = 0;
// Disabled state
states[i] = new int[]{-android.R.attr.state_enabled};
colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorControlNormal);
i++;
states[i] = new int[]{-android.R.attr.state_pressed, -android.R.attr.state_focused};
colors[i] = getThemeAttrColor(mContext, R.attr.colorControlNormal);
i++;
states[i] = new int[0];
colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated);
i++;
return new ColorStateList(states, colors);
}
private static class ColorFilterLruCache extends LruCache<Integer, PorterDuffColorFilter> {
public ColorFilterLruCache(int maxSize) {
super(maxSize);
}
PorterDuffColorFilter get(int color, PorterDuff.Mode mode) {
return get(generateCacheKey(color, mode));
}
PorterDuffColorFilter put(int color, PorterDuff.Mode mode, PorterDuffColorFilter filter) {
return put(generateCacheKey(color, mode), filter);
}
private static int generateCacheKey(int color, PorterDuff.Mode mode) {
int hashCode = 1;
hashCode = 31 * hashCode + color;
hashCode = 31 * hashCode + mode.hashCode();
return hashCode;
}
}
public static void tintViewBackground(View view, TintInfo tint) {
final Drawable background = view.getBackground();
if (tint.mTintList != null) {
tintDrawableUsingColorFilter(
background,
tint.mTintList.getColorForState(view.getDrawableState(),
tint.mTintList.getDefaultColor()),
tint.mTintMode != null ? tint.mTintMode : DEFAULT_MODE);
} else {
background.clearColorFilter();
}
}
private static void tintDrawableUsingColorFilter(Drawable drawable, int color,
PorterDuff.Mode mode) {
// First, lets see if the cache already contains the color filter
PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode);
if (filter == null) {
// Cache miss, so create a color filter and add it to the cache
filter = new PorterDuffColorFilter(color, mode);
COLOR_FILTER_CACHE.put(color, mode, filter);
}
drawable.setColorFilter(filter);
}
}

View file

@ -0,0 +1,147 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.support.annotation.Nullable;
import android.support.v4.view.TintableBackgroundView;
import android.util.AttributeSet;
import android.widget.MultiAutoCompleteTextView;
/**
* An tint aware {@link android.widget.MultiAutoCompleteTextView}.
* <p>
* This will automatically be used when you use {@link android.widget.MultiAutoCompleteTextView}
* in your layouts. You should only need to manually use this class when writing custom views.
*/
public class TintMultiAutoCompleteTextView extends MultiAutoCompleteTextView
implements TintableBackgroundView {
private static final int[] TINT_ATTRS = {
android.R.attr.background,
android.R.attr.popupBackground
};
private TintManager mTintManager;
private TintInfo mBackgroundTint;
public TintMultiAutoCompleteTextView(Context context) {
this(context, null);
}
public TintMultiAutoCompleteTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.autoCompleteTextViewStyle);
}
public TintMultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
if (TintManager.SHOULD_BE_USED) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
TINT_ATTRS, defStyleAttr, 0);
mTintManager = a.getTintManager();
if (a.hasValue(0)) {
setSupportBackgroundTintList(
mTintManager.getColorStateList(a.getResourceId(0, -1)));
}
if (a.hasValue(1)) {
setDropDownBackgroundDrawable(a.getDrawable(1));
}
a.recycle();
}
}
@Override
public void setDropDownBackgroundResource(int id) {
if (mTintManager != null) {
setDropDownBackgroundDrawable(mTintManager.getDrawable(id));
} else {
super.setDropDownBackgroundResource(id);
}
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View,
* android.content.res.ColorStateList)}
*
* @hide
*/
@Override
public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
if (mBackgroundTint == null) {
mBackgroundTint = new TintInfo();
}
mBackgroundTint.mTintList = tint;
applySupportBackgroundTint();
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)}
*
* @hide
*/
@Override
@Nullable
public ColorStateList getSupportBackgroundTintList() {
return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)}
*
* @hide
*/
@Override
public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
if (mBackgroundTint == null) {
mBackgroundTint = new TintInfo();
}
mBackgroundTint.mTintMode = tintMode;
applySupportBackgroundTint();
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)}
*
* @hide
*/
@Override
@Nullable
public PorterDuff.Mode getSupportBackgroundTintMode() {
return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
applySupportBackgroundTint();
}
private void applySupportBackgroundTint() {
if (getBackground() != null && mBackgroundTint != null) {
TintManager.tintViewBackground(this, mBackgroundTint);
}
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RadioButton;
/**
* An tint aware {@link android.widget.RadioButton}.
* <p>
* This will automatically be used when you use {@link android.widget.RadioButton} in your
* layouts. You should only need to manually use this class when writing custom views.
*/
public class TintRadioButton extends RadioButton {
private static final int[] TINT_ATTRS = {
android.R.attr.button
};
private TintManager mTintManager;
public TintRadioButton(Context context) {
this(context, null);
}
public TintRadioButton(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.radioButtonStyle);
}
public TintRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (TintManager.SHOULD_BE_USED) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
TINT_ATTRS, defStyleAttr, 0);
setButtonDrawable(a.getDrawable(0));
a.recycle();
mTintManager = a.getTintManager();
}
}
@Override
public void setButtonDrawable(int resid) {
if (mTintManager != null) {
setButtonDrawable(mTintManager.getDrawable(resid));
} else {
super.setButtonDrawable(resid);
}
}
}

View file

@ -0,0 +1,166 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Shader;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.shapes.Shape;
import android.support.v4.graphics.drawable.DrawableWrapper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.RatingBar;
/**
* An tint aware {@link android.widget.RatingBar}.
* <p>
* This will automatically be used when you use {@link android.widget.RatingBar} in your
* layouts. You should only need to manually use this class when writing custom views.
*/
public class TintRatingBar extends RatingBar {
private static final int[] TINT_ATTRS = {
android.R.attr.indeterminateDrawable,
android.R.attr.progressDrawable
};
private Bitmap mSampleTile;
public TintRatingBar(Context context) {
this(context, null);
}
public TintRatingBar(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.ratingBarStyle);
}
public TintRatingBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (TintManager.SHOULD_BE_USED) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
TINT_ATTRS, defStyleAttr, 0);
Drawable drawable = a.getDrawable(0);
if (drawable != null) {
setIndeterminateDrawable(tileifyIndeterminate(drawable));
}
drawable = a.getDrawable(1);
if (drawable != null) {
setProgressDrawable(tileify(drawable, false));
}
a.recycle();
}
}
/**
* Converts a drawable to a tiled version of itself. It will recursively
* traverse layer and state list drawables.
*/
private Drawable tileify(Drawable drawable, boolean clip) {
if (drawable instanceof DrawableWrapper) {
Drawable inner = ((DrawableWrapper) drawable).getWrappedDrawable();
if (inner != null) {
inner = tileify(inner, clip);
((DrawableWrapper) drawable).setWrappedDrawable(inner);
}
} else if (drawable instanceof LayerDrawable) {
LayerDrawable background = (LayerDrawable) drawable;
final int N = background.getNumberOfLayers();
Drawable[] outDrawables = new Drawable[N];
for (int i = 0; i < N; i++) {
int id = background.getId(i);
outDrawables[i] = tileify(background.getDrawable(i),
(id == android.R.id.progress || id == android.R.id.secondaryProgress));
}
LayerDrawable newBg = new LayerDrawable(outDrawables);
for (int i = 0; i < N; i++) {
newBg.setId(i, background.getId(i));
}
return newBg;
} else if (drawable instanceof BitmapDrawable) {
final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
if (mSampleTile == null) {
mSampleTile = tileBitmap;
}
final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShader);
return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
ClipDrawable.HORIZONTAL) : shapeDrawable;
}
return drawable;
}
/**
* Convert a AnimationDrawable for use as a barberpole animation.
* Each frame of the animation is wrapped in a ClipDrawable and
* given a tiling BitmapShader.
*/
private Drawable tileifyIndeterminate(Drawable drawable) {
if (drawable instanceof AnimationDrawable) {
AnimationDrawable background = (AnimationDrawable) drawable;
final int N = background.getNumberOfFrames();
AnimationDrawable newBg = new AnimationDrawable();
newBg.setOneShot(background.isOneShot());
for (int i = 0; i < N; i++) {
Drawable frame = tileify(background.getFrame(i), true);
frame.setLevel(10000);
newBg.addFrame(frame, background.getDuration(i));
}
newBg.setLevel(10000);
drawable = newBg;
}
return drawable;
}
private Shape getDrawableShape() {
final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
return new RoundRectShape(roundedCorners, null, null);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mSampleTile != null) {
final int width = mSampleTile.getWidth() * getNumStars();
setMeasuredDimension(ViewCompat.resolveSizeAndState(width, widthMeasureSpec, 0),
getMeasuredHeight());
}
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.support.v7.appcompat.R;
/**
* This class allows us to intercept calls so that we can tint resources (if applicable).
*
* @hide
*/
class TintResources extends ResourcesWrapper {
private final TintManager mTintManager;
public TintResources(Resources resources, TintManager tintManager) {
super(resources);
mTintManager = tintManager;
}
/**
* We intercept this call so that we tint the result (if applicable). This is needed for things
* like {@link DrawableContainer}s which retrieve their children via this method.
*/
@Override
public Drawable getDrawable(int id) throws NotFoundException {
Drawable d = super.getDrawable(id);
if (d != null) {
mTintManager.tintDrawable(id, d);
}
return d;
}
}

View file

@ -0,0 +1,163 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v4.view.TintableBackgroundView;
import android.util.AttributeSet;
import android.widget.ListPopupWindow;
import android.widget.Spinner;
import java.lang.reflect.Field;
/**
* An tint aware {@link android.widget.Spinner}.
* <p>
* This will automatically be used when you use {@link android.widget.Spinner} in your
* layouts. You should only need to manually use this class when writing custom views.
*/
public class TintSpinner extends Spinner implements TintableBackgroundView {
private static final int[] TINT_ATTRS = {
android.R.attr.background,
android.R.attr.popupBackground
};
private TintInfo mBackgroundTint;
public TintSpinner(Context context) {
this(context, null);
}
public TintSpinner(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.spinnerStyle);
}
public TintSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (TintManager.SHOULD_BE_USED) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
TINT_ATTRS, defStyleAttr, 0);
if (a.hasValue(0)) {
setSupportBackgroundTintList(
a.getTintManager().getColorStateList(a.getResourceId(0, -1)));
}
if (a.hasValue(1)) {
final Drawable popupBackground = a.getDrawable(1);
if (Build.VERSION.SDK_INT >= 16) {
setPopupBackgroundDrawable(popupBackground);
} else if (Build.VERSION.SDK_INT >= 11) {
setPopupBackgroundDrawableV11(this, popupBackground);
}
}
a.recycle();
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static void setPopupBackgroundDrawableV11(Spinner view, Drawable background) {
try {
Field popupField = Spinner.class.getDeclaredField("mPopup");
popupField.setAccessible(true);
Object popup = popupField.get(view);
if (popup instanceof ListPopupWindow) {
((ListPopupWindow) popup).setBackgroundDrawable(background);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View,
* android.content.res.ColorStateList)}
*
* @hide
*/
@Override
public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
if (mBackgroundTint == null && tint != null) {
mBackgroundTint = new TintInfo();
}
mBackgroundTint.mTintList = tint;
applySupportBackgroundTint();
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)}
*
* @hide
*/
@Override
@Nullable
public ColorStateList getSupportBackgroundTintList() {
return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)}
*
* @hide
*/
@Override
public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
if (mBackgroundTint == null) {
mBackgroundTint = new TintInfo();
}
mBackgroundTint.mTintMode = tintMode;
applySupportBackgroundTint();
}
/**
* This should be accessed via
* {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)}
*
* @hide
*/
@Override
@Nullable
public PorterDuff.Mode getSupportBackgroundTintMode() {
return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
applySupportBackgroundTint();
}
private void applySupportBackgroundTint() {
if (getBackground() != null && mBackgroundTint != null) {
TintManager.tintViewBackground(this, mBackgroundTint);
}
}
}

View file

@ -0,0 +1,189 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
/**
* A class that wraps a {@link android.content.res.TypedArray} and provides the same public API
* surface. The purpose of this class is so that we can intercept the {@link #getDrawable(int)}
* call and tint the result.
*
* @hide
*/
public class TintTypedArray {
private final Context mContext;
private final TypedArray mWrapped;
private TintManager mTintManager;
public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set,
int[] attrs) {
TypedArray array = context.obtainStyledAttributes(set, attrs);
return new TintTypedArray(context, array);
}
public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set,
int[] attrs, int defStyleAttr, int defStyleRes) {
TypedArray array = context.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes);
return new TintTypedArray(context, array);
}
private TintTypedArray(Context context, TypedArray array) {
mContext = context;
mWrapped = array;
}
public Drawable getDrawable(int index) {
if (mWrapped.hasValue(index)) {
final int resourceId = mWrapped.getResourceId(index, 0);
if (resourceId != 0) {
return getTintManager().getDrawable(resourceId);
}
}
return mWrapped.getDrawable(index);
}
public int length() {
return mWrapped.length();
}
public int getIndexCount() {
return mWrapped.getIndexCount();
}
public int getIndex(int at) {
return mWrapped.getIndex(at);
}
public Resources getResources() {
return mWrapped.getResources();
}
public CharSequence getText(int index) {
return mWrapped.getText(index);
}
public String getString(int index) {
return mWrapped.getString(index);
}
public String getNonResourceString(int index) {
return mWrapped.getNonResourceString(index);
}
public boolean getBoolean(int index, boolean defValue) {
return mWrapped.getBoolean(index, defValue);
}
public int getInt(int index, int defValue) {
return mWrapped.getInt(index, defValue);
}
public float getFloat(int index, float defValue) {
return mWrapped.getFloat(index, defValue);
}
public int getColor(int index, int defValue) {
return mWrapped.getColor(index, defValue);
}
public ColorStateList getColorStateList(int index) {
return mWrapped.getColorStateList(index);
}
public int getInteger(int index, int defValue) {
return mWrapped.getInteger(index, defValue);
}
public float getDimension(int index, float defValue) {
return mWrapped.getDimension(index, defValue);
}
public int getDimensionPixelOffset(int index, int defValue) {
return mWrapped.getDimensionPixelOffset(index, defValue);
}
public int getDimensionPixelSize(int index, int defValue) {
return mWrapped.getDimensionPixelSize(index, defValue);
}
public int getLayoutDimension(int index, String name) {
return mWrapped.getLayoutDimension(index, name);
}
public int getLayoutDimension(int index, int defValue) {
return mWrapped.getLayoutDimension(index, defValue);
}
public float getFraction(int index, int base, int pbase, float defValue) {
return mWrapped.getFraction(index, base, pbase, defValue);
}
public int getResourceId(int index, int defValue) {
return mWrapped.getResourceId(index, defValue);
}
public CharSequence[] getTextArray(int index) {
return mWrapped.getTextArray(index);
}
public boolean getValue(int index, TypedValue outValue) {
return mWrapped.getValue(index, outValue);
}
public int getType(int index) {
return mWrapped.getType(index);
}
public boolean hasValue(int index) {
return mWrapped.hasValue(index);
}
public TypedValue peekValue(int index) {
return mWrapped.peekValue(index);
}
public String getPositionDescription() {
return mWrapped.getPositionDescription();
}
public void recycle() {
mWrapped.recycle();
}
public int getChangingConfigurations() {
return mWrapped.getChangingConfigurations();
}
public TintManager getTintManager() {
if (mTintManager == null) {
mTintManager = (mContext instanceof TintContextWrapper)
? ((TintContextWrapper) mContext).getTintManager()
: new TintManager(mContext);
}
return mTintManager;
}
}

View file

@ -0,0 +1,716 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.app.ActionBar;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.support.v7.appcompat.R;
import android.support.v7.internal.view.menu.ActionMenuItem;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.view.menu.MenuPresenter;
import android.support.v7.widget.ActionMenuPresenter;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.SpinnerAdapter;
/**
* Internal class used to interact with the Toolbar widget without
* exposing interface methods to the public API.
*
* <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView
* so that either variant acting as a
* {@link android.support.v7.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave
* in the same way.</p>
*
* @hide
*/
public class ToolbarWidgetWrapper implements DecorToolbar {
private static final String TAG = "ToolbarWidgetWrapper";
private static final int AFFECTS_LOGO_MASK =
ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO;
private Toolbar mToolbar;
private int mDisplayOpts;
private View mTabView;
private SpinnerCompat mSpinner;
private View mCustomView;
private Drawable mIcon;
private Drawable mLogo;
private Drawable mNavIcon;
private boolean mTitleSet;
private CharSequence mTitle;
private CharSequence mSubtitle;
private CharSequence mHomeDescription;
private Window.Callback mWindowCallback;
private boolean mMenuPrepared;
private ActionMenuPresenter mActionMenuPresenter;
private int mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
private final TintManager mTintManager;
private int mDefaultNavigationContentDescription = 0;
private Drawable mDefaultNavigationIcon;
public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) {
this(toolbar, style, R.string.abc_action_bar_up_description,
R.drawable.abc_ic_ab_back_mtrl_am_alpha);
}
public ToolbarWidgetWrapper(Toolbar toolbar, boolean style,
int defaultNavigationContentDescription, int defaultNavigationIcon) {
mToolbar = toolbar;
mTitle = toolbar.getTitle();
mSubtitle = toolbar.getSubtitle();
mTitleSet = mTitle != null;
mNavIcon = toolbar.getNavigationIcon();
if (style) {
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(toolbar.getContext(),
null, R.styleable.ActionBar, R.attr.actionBarStyle, 0);
final CharSequence title = a.getText(R.styleable.ActionBar_title);
if (!TextUtils.isEmpty(title)) {
setTitle(title);
}
final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle);
if (!TextUtils.isEmpty(subtitle)) {
setSubtitle(subtitle);
}
final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo);
if (logo != null) {
setLogo(logo);
}
final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon);
if (mNavIcon == null && icon != null) {
setIcon(icon);
}
final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator);
if (navIcon != null) {
setNavigationIcon(navIcon);
}
setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0));
final int customNavId = a.getResourceId(
R.styleable.ActionBar_customNavigationLayout, 0);
if (customNavId != 0) {
setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId,
mToolbar, false));
setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM);
}
final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
if (height > 0) {
final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams();
lp.height = height;
mToolbar.setLayoutParams(lp);
}
final int contentInsetStart = a.getDimensionPixelOffset(
R.styleable.ActionBar_contentInsetStart, -1);
final int contentInsetEnd = a.getDimensionPixelOffset(
R.styleable.ActionBar_contentInsetEnd, -1);
if (contentInsetStart >= 0 || contentInsetEnd >= 0) {
mToolbar.setContentInsetsRelative(Math.max(contentInsetStart, 0),
Math.max(contentInsetEnd, 0));
}
final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
if (titleTextStyle != 0) {
mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle);
}
final int subtitleTextStyle = a.getResourceId(
R.styleable.ActionBar_subtitleTextStyle, 0);
if (subtitleTextStyle != 0) {
mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle);
}
final int popupTheme = a.getResourceId(R.styleable.ActionBar_popupTheme, 0);
if (popupTheme != 0) {
mToolbar.setPopupTheme(popupTheme);
}
a.recycle();
// Keep the TintManager in case we need it later
mTintManager = a.getTintManager();
} else {
mDisplayOpts = detectDisplayOptions();
// Create a TintManager in case we need it later
mTintManager = new TintManager(toolbar.getContext());
}
setDefaultNavigationContentDescription(defaultNavigationContentDescription);
mHomeDescription = mToolbar.getNavigationContentDescription();
setDefaultNavigationIcon(mTintManager.getDrawable(defaultNavigationIcon));
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
0, android.R.id.home, 0, 0, mTitle);
@Override
public void onClick(View v) {
if (mWindowCallback != null && mMenuPrepared) {
mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem);
}
}
});
}
/**
* Sets the default content description for the navigation button.
* <p>
* It changes the current content description if and only if the provided resource id is
* different than the current default resource id and the current content description is empty.
*
* @param defaultNavigationContentDescription The resource id for the default content
* description
*/
@Override
public void setDefaultNavigationContentDescription(int defaultNavigationContentDescription) {
if (defaultNavigationContentDescription == mDefaultNavigationContentDescription) {
return;
}
mDefaultNavigationContentDescription = defaultNavigationContentDescription;
if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) {
setNavigationContentDescription(mDefaultNavigationContentDescription);
}
}
@Override
public void setDefaultNavigationIcon(Drawable defaultNavigationIcon) {
if (mDefaultNavigationIcon != defaultNavigationIcon) {
mDefaultNavigationIcon = defaultNavigationIcon;
updateNavigationIcon();
}
}
private int detectDisplayOptions() {
int opts = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME |
ActionBar.DISPLAY_USE_LOGO;
if (mToolbar.getNavigationIcon() != null) {
opts |= ActionBar.DISPLAY_HOME_AS_UP;
}
return opts;
}
@Override
public ViewGroup getViewGroup() {
return mToolbar;
}
@Override
public Context getContext() {
return mToolbar.getContext();
}
@Override
public boolean isSplit() {
return false;
}
@Override
public boolean hasExpandedActionView() {
return mToolbar.hasExpandedActionView();
}
@Override
public void collapseActionView() {
mToolbar.collapseActionView();
}
@Override
public void setWindowCallback(Window.Callback cb) {
mWindowCallback = cb;
}
@Override
public void setWindowTitle(CharSequence title) {
// "Real" title always trumps window title.
if (!mTitleSet) {
setTitleInt(title);
}
}
@Override
public CharSequence getTitle() {
return mToolbar.getTitle();
}
@Override
public void setTitle(CharSequence title) {
mTitleSet = true;
setTitleInt(title);
}
private void setTitleInt(CharSequence title) {
mTitle = title;
if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
mToolbar.setTitle(title);
}
}
@Override
public CharSequence getSubtitle() {
return mToolbar.getSubtitle();
}
@Override
public void setSubtitle(CharSequence subtitle) {
mSubtitle = subtitle;
if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
mToolbar.setSubtitle(subtitle);
}
}
@Override
public void initProgress() {
Log.i(TAG, "Progress display unsupported");
}
@Override
public void initIndeterminateProgress() {
Log.i(TAG, "Progress display unsupported");
}
@Override
public boolean canSplit() {
return false;
}
@Override
public void setSplitView(ViewGroup splitView) {
}
@Override
public void setSplitToolbar(boolean split) {
if (split) {
throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar");
}
}
@Override
public void setSplitWhenNarrow(boolean splitWhenNarrow) {
// Ignore.
}
@Override
public boolean hasIcon() {
return mIcon != null;
}
@Override
public boolean hasLogo() {
return mLogo != null;
}
@Override
public void setIcon(int resId) {
setIcon(resId != 0 ? mTintManager.getDrawable(resId) : null);
}
@Override
public void setIcon(Drawable d) {
mIcon = d;
updateToolbarLogo();
}
@Override
public void setLogo(int resId) {
setLogo(resId != 0 ? mTintManager.getDrawable(resId) : null);
}
@Override
public void setLogo(Drawable d) {
mLogo = d;
updateToolbarLogo();
}
private void updateToolbarLogo() {
Drawable logo = null;
if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) {
if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) {
logo = mLogo != null ? mLogo : mIcon;
} else {
logo = mIcon;
}
}
mToolbar.setLogo(logo);
}
@Override
public boolean canShowOverflowMenu() {
return mToolbar.canShowOverflowMenu();
}
@Override
public boolean isOverflowMenuShowing() {
return mToolbar.isOverflowMenuShowing();
}
@Override
public boolean isOverflowMenuShowPending() {
return mToolbar.isOverflowMenuShowPending();
}
@Override
public boolean showOverflowMenu() {
return mToolbar.showOverflowMenu();
}
@Override
public boolean hideOverflowMenu() {
return mToolbar.hideOverflowMenu();
}
@Override
public void setMenuPrepared() {
mMenuPrepared = true;
}
@Override
public void setMenu(Menu menu, MenuPresenter.Callback cb) {
if (mActionMenuPresenter == null) {
mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext());
mActionMenuPresenter.setId(R.id.action_menu_presenter);
}
mActionMenuPresenter.setCallback(cb);
mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter);
}
@Override
public void dismissPopupMenus() {
mToolbar.dismissPopupMenus();
}
@Override
public int getDisplayOptions() {
return mDisplayOpts;
}
@Override
public void setDisplayOptions(int newOpts) {
final int oldOpts = mDisplayOpts;
final int changed = oldOpts ^ newOpts;
mDisplayOpts = newOpts;
if (changed != 0) {
if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
updateNavigationIcon();
updateHomeAccessibility();
} else {
mToolbar.setNavigationIcon(null);
}
}
if ((changed & AFFECTS_LOGO_MASK) != 0) {
updateToolbarLogo();
}
if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
mToolbar.setTitle(mTitle);
mToolbar.setSubtitle(mSubtitle);
} else {
mToolbar.setTitle(null);
mToolbar.setSubtitle(null);
}
}
if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) {
if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
mToolbar.addView(mCustomView);
} else {
mToolbar.removeView(mCustomView);
}
}
}
}
@Override
public void setEmbeddedTabView(ScrollingTabContainerView tabView) {
if (mTabView != null && mTabView.getParent() == mToolbar) {
mToolbar.removeView(mTabView);
}
mTabView = tabView;
if (tabView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
mToolbar.addView(mTabView, 0);
Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
lp.gravity = Gravity.START | Gravity.BOTTOM;
tabView.setAllowCollapse(true);
}
}
@Override
public boolean hasEmbeddedTabs() {
return mTabView != null;
}
@Override
public boolean isTitleTruncated() {
return mToolbar.isTitleTruncated();
}
@Override
public void setCollapsible(boolean collapsible) {
mToolbar.setCollapsible(collapsible);
}
@Override
public void setHomeButtonEnabled(boolean enable) {
// Ignore
}
@Override
public int getNavigationMode() {
return mNavigationMode;
}
@Override
public void setNavigationMode(int mode) {
final int oldMode = mNavigationMode;
if (mode != oldMode) {
switch (oldMode) {
case ActionBar.NAVIGATION_MODE_LIST:
if (mSpinner != null && mSpinner.getParent() == mToolbar) {
mToolbar.removeView(mSpinner);
}
break;
case ActionBar.NAVIGATION_MODE_TABS:
if (mTabView != null && mTabView.getParent() == mToolbar) {
mToolbar.removeView(mTabView);
}
break;
}
mNavigationMode = mode;
switch (mode) {
case ActionBar.NAVIGATION_MODE_STANDARD:
break;
case ActionBar.NAVIGATION_MODE_LIST:
ensureSpinner();
mToolbar.addView(mSpinner, 0);
break;
case ActionBar.NAVIGATION_MODE_TABS:
if (mTabView != null) {
mToolbar.addView(mTabView, 0);
Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
lp.gravity = Gravity.START | Gravity.BOTTOM;
}
break;
default:
throw new IllegalArgumentException("Invalid navigation mode " + mode);
}
}
}
private void ensureSpinner() {
if (mSpinner == null) {
mSpinner = new SpinnerCompat(getContext(), null, R.attr.actionDropDownStyle);
Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
mSpinner.setLayoutParams(lp);
}
}
@Override
public void setDropdownParams(SpinnerAdapter adapter,
AdapterViewCompat.OnItemSelectedListener listener) {
ensureSpinner();
mSpinner.setAdapter(adapter);
mSpinner.setOnItemSelectedListener(listener);
}
@Override
public void setDropdownSelectedPosition(int position) {
if (mSpinner == null) {
throw new IllegalStateException(
"Can't set dropdown selected position without an adapter");
}
mSpinner.setSelection(position);
}
@Override
public int getDropdownSelectedPosition() {
return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0;
}
@Override
public int getDropdownItemCount() {
return mSpinner != null ? mSpinner.getCount() : 0;
}
@Override
public void setCustomView(View view) {
if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
mToolbar.removeView(mCustomView);
}
mCustomView = view;
if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
mToolbar.addView(mCustomView);
}
}
@Override
public View getCustomView() {
return mCustomView;
}
@Override
public void animateToVisibility(int visibility) {
if (visibility == View.GONE) {
ViewCompat.animate(mToolbar).alpha(0)
.setListener(new ViewPropertyAnimatorListenerAdapter() {
private boolean mCanceled = false;
@Override
public void onAnimationEnd(View view) {
if (!mCanceled) {
mToolbar.setVisibility(View.GONE);
}
}
@Override
public void onAnimationCancel(View view) {
mCanceled = true;
}
});
} else if (visibility == View.VISIBLE) {
ViewCompat.animate(mToolbar).alpha(1)
.setListener(new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationStart(View view) {
mToolbar.setVisibility(View.VISIBLE);
}
});
}
}
@Override
public void setNavigationIcon(Drawable icon) {
mNavIcon = icon;
updateNavigationIcon();
}
@Override
public void setNavigationIcon(int resId) {
setNavigationIcon(resId != 0
? mTintManager.getDrawable(resId)
: null);
}
@Override
public void setNavigationContentDescription(CharSequence description) {
mHomeDescription = description;
updateHomeAccessibility();
}
@Override
public void setNavigationContentDescription(int resId) {
setNavigationContentDescription(resId == 0 ? null : getContext().getString(resId));
}
private void updateHomeAccessibility() {
if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
if (TextUtils.isEmpty(mHomeDescription)) {
mToolbar.setNavigationContentDescription(mDefaultNavigationContentDescription);
} else {
mToolbar.setNavigationContentDescription(mHomeDescription);
}
}
}
private void updateNavigationIcon() {
if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
mToolbar.setNavigationIcon(mNavIcon != null ? mNavIcon : mDefaultNavigationIcon);
}
}
@Override
public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) {
mToolbar.saveHierarchyState(toolbarStates);
}
@Override
public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) {
mToolbar.restoreHierarchyState(toolbarStates);
}
@Override
public void setBackgroundDrawable(Drawable d) {
//noinspection deprecation
mToolbar.setBackgroundDrawable(d);
}
@Override
public int getHeight() {
return mToolbar.getHeight();
}
@Override
public void setVisibility(int visible) {
mToolbar.setVisibility(visible);
}
@Override
public int getVisibility() {
return mToolbar.getVisibility();
}
@Override
public void setMenuCallbacks(MenuPresenter.Callback actionMenuPresenterCallback,
MenuBuilder.Callback menuBuilderCallback) {
mToolbar.setMenuCallbacks(actionMenuPresenterCallback, menuBuilderCallback);
}
@Override
public Menu getMenu() {
return mToolbar.getMenu();
}
@Override
public int getPopupTheme() {
return mToolbar.getPopupTheme();
}
}

View file

@ -0,0 +1,262 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import java.lang.ref.WeakReference;
/**
* Backport of {@link android.view.ViewStub} so that we can set the
* {@link android.view.LayoutInflater} on devices before Jelly Bean.
*
* @hide
*/
public final class ViewStubCompat extends View {
private int mLayoutResource = 0;
private int mInflatedId;
private WeakReference<View> mInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
public ViewStubCompat(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ViewStubCompat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStubCompat,
defStyle, 0);
mInflatedId = a.getResourceId(R.styleable.ViewStubCompat_android_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStubCompat_android_layout, 0);
setId(a.getResourceId(R.styleable.ViewStubCompat_android_id, NO_ID));
a.recycle();
setVisibility(GONE);
setWillNotDraw(true);
}
/**
* Returns the id taken by the inflated view. If the inflated id is
* {@link View#NO_ID}, the inflated view keeps its original id.
*
* @return A positive integer used to identify the inflated view or
* {@link #NO_ID} if the inflated view should keep its id.
*
* @see #setInflatedId(int)
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
public int getInflatedId() {
return mInflatedId;
}
/**
* Defines the id taken by the inflated view. If the inflated id is
* {@link View#NO_ID}, the inflated view keeps its original id.
*
* @param inflatedId A positive integer used to identify the inflated view or
* {@link #NO_ID} if the inflated view should keep its id.
*
* @see #getInflatedId()
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
public void setInflatedId(int inflatedId) {
mInflatedId = inflatedId;
}
/**
* Returns the layout resource that will be used by {@link #setVisibility(int)} or
* {@link #inflate()} to replace this StubbedView
* in its parent by another view.
*
* @return The layout resource identifier used to inflate the new View.
*
* @see #setLayoutResource(int)
* @see #setVisibility(int)
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
public int getLayoutResource() {
return mLayoutResource;
}
/**
* Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
* or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
* used to replace this StubbedView in its parent.
*
* @param layoutResource A valid layout resource identifier (different from 0.)
*
* @see #getLayoutResource()
* @see #setVisibility(int)
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
public void setLayoutResource(int layoutResource) {
mLayoutResource = layoutResource;
}
/**
* Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
* to use the default.
*/
public void setLayoutInflater(LayoutInflater inflater) {
mInflater = inflater;
}
/**
* Get current {@link LayoutInflater} used in {@link #inflate()}.
*/
public LayoutInflater getLayoutInflater() {
return mInflater;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
/**
* When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
* {@link #inflate()} is invoked and this StubbedView is replaced in its parent
* by the inflated layout resource. After that calls to this function are passed
* through to the inflated view.
*
* @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
*
* @see #inflate()
*/
@Override
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
/**
* Inflates the layout resource identified by {@link #getLayoutResource()}
* and replaces this StubbedView in its parent by the inflated layout resource.
*
* @return The inflated layout resource.
*
*/
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(getContext());
}
final View view = factory.inflate(mLayoutResource, parent,
false);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
mInflatedViewRef = new WeakReference<View>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
/**
* Specifies the inflate listener to be notified after this ViewStub successfully
* inflated its layout resource.
*
* @param inflateListener The OnInflateListener to notify of successful inflation.
*
* @see android.view.ViewStub.OnInflateListener
*/
public void setOnInflateListener(OnInflateListener inflateListener) {
mInflateListener = inflateListener;
}
/**
* Listener used to receive a notification after a ViewStub has successfully
* inflated its layout resource.
*
* @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
*/
public static interface OnInflateListener {
/**
* Invoked after a ViewStub successfully inflated its layout resource.
* This method is invoked after the inflated view was added to the
* hierarchy but before the layout pass.
*
* @param stub The ViewStub that initiated the inflation.
* @param inflated The inflated View.
*/
void onInflate(ViewStubCompat stub, View inflated);
}
}

View file

@ -0,0 +1,140 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.support.v7.internal.view.ContextThemeWrapper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @hide
*/
public class ViewUtils {
private static final String TAG = "ViewUtils";
private static Method sComputeFitSystemWindowsMethod;
static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
try {
sComputeFitSystemWindowsMethod = View.class.getDeclaredMethod(
"computeFitSystemWindows", Rect.class, Rect.class);
if (!sComputeFitSystemWindowsMethod.isAccessible()) {
sComputeFitSystemWindowsMethod.setAccessible(true);
}
} catch (NoSuchMethodException e) {
Log.d(TAG, "Could not find method computeFitSystemWindows. Oh well.");
}
}
}
private ViewUtils() {}
public static boolean isLayoutRtl(View view) {
return ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL;
}
/**
* Merge two states as returned by {@link ViewCompat#getMeasuredState(android.view.View)} ()}.
* @param curState The current state as returned from a view or the result
* of combining multiple views.
* @param newState The new view state to combine.
* @return Returns a new integer reflecting the combination of the two
* states.
*/
public static int combineMeasuredStates(int curState, int newState) {
return curState | newState;
}
/**
* Allow calling the hidden method {@code computeFitSystemWindows(Rect, Rect)} through
* reflection on {@code view}.
*/
public static void computeFitSystemWindows(View view, Rect inoutInsets, Rect outLocalInsets) {
if (sComputeFitSystemWindowsMethod != null) {
try {
sComputeFitSystemWindowsMethod.invoke(view, inoutInsets, outLocalInsets);
} catch (Exception e) {
Log.d(TAG, "Could not invoke computeFitSystemWindows", e);
}
}
}
/**
* Allow calling the hidden method {@code makeOptionalFitsSystem()} through reflection on
* {@code view}.
*/
public static void makeOptionalFitsSystemWindows(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
try {
// We need to use getMethod() for makeOptionalFitsSystemWindows since both View
// and ViewGroup implement the method
Method method = view.getClass().getMethod("makeOptionalFitsSystemWindows");
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(view);
} catch (NoSuchMethodException e) {
Log.d(TAG, "Could not find method makeOptionalFitsSystemWindows. Oh well...");
} catch (InvocationTargetException e) {
Log.d(TAG, "Could not invoke makeOptionalFitsSystemWindows", e);
} catch (IllegalAccessException e) {
Log.d(TAG, "Could not invoke makeOptionalFitsSystemWindows", e);
}
}
}
/**
* Allows us to emulate the {@code android:theme} attribute for devices before L.
*/
public static Context themifyContext(Context context, AttributeSet attrs,
boolean useAndroidTheme, boolean useAppTheme) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.View, 0, 0);
int themeId = 0;
if (useAndroidTheme) {
// First try reading android:theme if enabled
themeId = a.getResourceId(R.styleable.View_android_theme, 0);
}
if (useAppTheme && themeId == 0) {
// ...if that didn't work, try reading app:theme (for legacy reasons) if enabled
themeId = a.getResourceId(R.styleable.View_theme, 0);
if (themeId != 0) {
Log.i(TAG, "app:theme is now deprecated. Please move to using android:theme instead.");
}
}
a.recycle();
if (themeId != 0 && (!(context instanceof ContextThemeWrapper)
|| ((ContextThemeWrapper) context).getThemeResId() != themeId)) {
// If the context isn't a ContextThemeWrapperCompat, or it is but does not have
// the same theme as we need, wrap it in a new wrapper
context = new ContextThemeWrapper(context, themeId);
}
return context;
}
}

View file

@ -0,0 +1,279 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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 android.support.v7.view;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
/**
* Represents a contextual mode of the user interface. Action modes can be used to provide
* alternative interaction modes and replace parts of the normal UI until finished.
* Examples of good action modes include text selection and contextual actions.
* <div class="special reference">
*
* <h3>Developer Guides</h3>
* <p>For information about how to provide contextual actions with {@code ActionMode},
* read the <a href="{@docRoot}guide/topics/ui/menus.html#context-menu">Menus</a>
* developer guide.</p>
*
* </div>
*/
public abstract class ActionMode {
private Object mTag;
private boolean mTitleOptionalHint;
/**
* Set a tag object associated with this ActionMode.
*
* <p>Like the tag available to views, this allows applications to associate arbitrary
* data with an ActionMode for later reference.
*
* @param tag Tag to associate with this ActionMode
*
* @see #getTag()
*/
public void setTag(Object tag) {
mTag = tag;
}
/**
* Retrieve the tag object associated with this ActionMode.
*
* <p>Like the tag available to views, this allows applications to associate arbitrary
* data with an ActionMode for later reference.
*
* @return Tag associated with this ActionMode
*
* @see #setTag(Object)
*/
public Object getTag() {
return mTag;
}
/**
* Set the title of the action mode. This method will have no visible effect if
* a custom view has been set.
*
* @param title Title string to set
*
* @see #setTitle(int)
* @see #setCustomView(View)
*/
public abstract void setTitle(CharSequence title);
/**
* Set the title of the action mode. This method will have no visible effect if
* a custom view has been set.
*
* @param resId Resource ID of a string to set as the title
*
* @see #setTitle(CharSequence)
* @see #setCustomView(View)
*/
public abstract void setTitle(int resId);
/**
* Set the subtitle of the action mode. This method will have no visible effect if
* a custom view has been set.
*
* @param subtitle Subtitle string to set
*
* @see #setSubtitle(int)
* @see #setCustomView(View)
*/
public abstract void setSubtitle(CharSequence subtitle);
/**
* Set the subtitle of the action mode. This method will have no visible effect if
* a custom view has been set.
*
* @param resId Resource ID of a string to set as the subtitle
*
* @see #setSubtitle(CharSequence)
* @see #setCustomView(View)
*/
public abstract void setSubtitle(int resId);
/**
* Set whether or not the title/subtitle display for this action mode
* is optional.
*
* <p>In many cases the supplied title for an action mode is merely
* meant to add context and is not strictly required for the action
* mode to be useful. If the title is optional, the system may choose
* to hide the title entirely rather than truncate it due to a lack
* of available space.</p>
*
* <p>Note that this is merely a hint; the underlying implementation
* may choose to ignore this setting under some circumstances.</p>
*
* @param titleOptional true if the title only presents optional information.
*/
public void setTitleOptionalHint(boolean titleOptional) {
mTitleOptionalHint = titleOptional;
}
/**
* @return true if this action mode has been given a hint to consider the
* title/subtitle display to be optional.
*
* @see #setTitleOptionalHint(boolean)
* @see #isTitleOptional()
*/
public boolean getTitleOptionalHint() {
return mTitleOptionalHint;
}
/**
* @return true if this action mode considers the title and subtitle fields
* as optional. Optional titles may not be displayed to the user.
*/
public boolean isTitleOptional() {
return false;
}
/**
* Set a custom view for this action mode. The custom view will take the place of
* the title and subtitle. Useful for things like search boxes.
*
* @param view Custom view to use in place of the title/subtitle.
*
* @see #setTitle(CharSequence)
* @see #setSubtitle(CharSequence)
*/
public abstract void setCustomView(View view);
/**
* Invalidate the action mode and refresh menu content. The mode's
* {@link ActionMode.Callback} will have its
* {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called.
* If it returns true the menu will be scanned for updated content and any relevant changes
* will be reflected to the user.
*/
public abstract void invalidate();
/**
* Finish and close this action mode. The action mode's {@link ActionMode.Callback} will
* have its {@link Callback#onDestroyActionMode(ActionMode)} method called.
*/
public abstract void finish();
/**
* Returns the menu of actions that this action mode presents.
*
* @return The action mode's menu.
*/
public abstract Menu getMenu();
/**
* Returns the current title of this action mode.
*
* @return Title text
*/
public abstract CharSequence getTitle();
/**
* Returns the current subtitle of this action mode.
*
* @return Subtitle text
*/
public abstract CharSequence getSubtitle();
/**
* Returns the current custom view for this action mode.
*
* @return The current custom view
*/
public abstract View getCustomView();
/**
* Returns a {@link MenuInflater} with the ActionMode's context.
*/
public abstract MenuInflater getMenuInflater();
/**
* Returns whether the UI presenting this action mode can take focus or not.
* This is used by internal components within the framework that would otherwise
* present an action mode UI that requires focus, such as an EditText as a custom view.
*
* @return true if the UI used to show this action mode can take focus
* @hide Internal use only
*/
public boolean isUiFocusable() {
return true;
}
/**
* Callback interface for action modes. Supplied to
* {@link android.support.v7.app.AppCompatDelegate#startSupportActionMode(Callback)} (Callback)},
* a Callback configures and handles events raised by a user's interaction with an action mode.
*
* <p>An action mode's lifecycle is as follows:
* <ul>
* <li>{@link Callback#onCreateActionMode(ActionMode, Menu)} once on initial
* creation</li>
* <li>{@link Callback#onPrepareActionMode(ActionMode, Menu)} after creation
* and any time the {@link ActionMode} is invalidated</li>
* <li>{@link Callback#onActionItemClicked(ActionMode, MenuItem)} any time a
* contextual action button is clicked</li>
* <li>{@link Callback#onDestroyActionMode(ActionMode)} when the action mode
* is closed</li>
* </ul>
*/
public interface Callback {
/**
* Called when action mode is first created. The menu supplied will be used to
* generate action buttons for the action mode.
*
* @param mode ActionMode being created
* @param menu Menu used to populate action buttons
* @return true if the action mode should be created, false if entering this
* mode should be aborted.
*/
public boolean onCreateActionMode(ActionMode mode, Menu menu);
/**
* Called to refresh an action mode's action menu whenever it is invalidated.
*
* @param mode ActionMode being prepared
* @param menu Menu used to populate action buttons
* @return true if the menu or action mode was updated, false otherwise.
*/
public boolean onPrepareActionMode(ActionMode mode, Menu menu);
/**
* Called to report a user click on an action button.
*
* @param mode The current ActionMode
* @param item The item that was clicked
* @return true if this callback handled the event, false if the standard MenuItem
* invocation should continue.
*/
public boolean onActionItemClicked(ActionMode mode, MenuItem item);
/**
* Called when an action mode is about to be exited and destroyed.
*
* @param mode The current ActionMode being destroyed
*/
public void onDestroyActionMode(ActionMode mode);
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 android.support.v7.view;
import android.support.v4.view.MenuItemCompat.OnActionExpandListener;
/**
* When a {@link android.view.View} implements this interface it will receive callbacks when expanded or
* collapsed as an action view alongside the optional, app-specified callbacks to {@link
* OnActionExpandListener}.
*
* <p>See {@link android.support.v4.view.MenuItemCompat} for more information about action views.
* See {@link android.app.ActionBar} for more information about the action bar.
*/
public interface CollapsibleActionView {
/**
* Called when this view is expanded as an action view. See {@link
* android.support.v4.view.MenuItemCompat#expandActionView(android.view.MenuItem)}.
*/
public void onActionViewExpanded();
/**
* Called when this view is collapsed as an action view. See {@link
* android.support.v4.view.MenuItemCompat#collapseActionView(android.view.MenuItem)}.
*/
public void onActionViewCollapsed();
}

View file

@ -0,0 +1,769 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.widget;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ActionProvider;
import android.support.v4.view.GravityCompat;
import android.support.v7.appcompat.R;
import android.support.v7.internal.transition.ActionBarTransition;
import android.support.v7.internal.view.ActionBarPolicy;
import android.support.v7.internal.view.menu.ActionMenuItemView;
import android.support.v7.internal.view.menu.BaseMenuPresenter;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.view.menu.MenuItemImpl;
import android.support.v7.internal.view.menu.MenuPopupHelper;
import android.support.v7.internal.view.menu.MenuView;
import android.support.v7.internal.view.menu.SubMenuBuilder;
import android.support.v7.internal.widget.TintImageView;
import android.util.SparseBooleanArray;
import android.view.MenuItem;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import java.util.ArrayList;
/**
* MenuPresenter for building action menus as seen in the action bar and action modes.
*
* @hide
*/
public class ActionMenuPresenter extends BaseMenuPresenter
implements ActionProvider.SubUiVisibilityListener {
private static final String TAG = "ActionMenuPresenter";
private View mOverflowButton;
private boolean mReserveOverflow;
private boolean mReserveOverflowSet;
private int mWidthLimit;
private int mActionItemWidthLimit;
private int mMaxItems;
private boolean mMaxItemsSet;
private boolean mStrictWidthLimit;
private boolean mWidthLimitSet;
private boolean mExpandedActionViewsExclusive;
private int mMinCellSize;
// Group IDs that have been added as actions - used temporarily, allocated here for reuse.
private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
private View mScrapActionButtonView;
private OverflowPopup mOverflowPopup;
private ActionButtonSubmenu mActionButtonPopup;
private OpenOverflowRunnable mPostedOpenRunnable;
private ActionMenuPopupCallback mPopupCallback;
final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
int mOpenSubMenuId;
public ActionMenuPresenter(Context context) {
super(context, R.layout.abc_action_menu_layout, R.layout.abc_action_menu_item_layout);
}
@Override
public void initForMenu(Context context, MenuBuilder menu) {
super.initForMenu(context, menu);
final Resources res = context.getResources();
final ActionBarPolicy abp = ActionBarPolicy.get(context);
if (!mReserveOverflowSet) {
mReserveOverflow = abp.showsOverflowMenuButton();
}
if (!mWidthLimitSet) {
mWidthLimit = abp.getEmbeddedMenuWidthLimit();
}
// Measure for initial configuration
if (!mMaxItemsSet) {
mMaxItems = abp.getMaxActionButtons();
}
int width = mWidthLimit;
if (mReserveOverflow) {
if (mOverflowButton == null) {
mOverflowButton = new OverflowMenuButton(mSystemContext);
final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
mOverflowButton.measure(spec, spec);
}
width -= mOverflowButton.getMeasuredWidth();
} else {
mOverflowButton = null;
}
mActionItemWidthLimit = width;
mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
// Drop a scrap view as it may no longer reflect the proper context/config.
mScrapActionButtonView = null;
}
public void onConfigurationChanged(Configuration newConfig) {
if (!mMaxItemsSet) {
mMaxItems = mContext.getResources().getInteger(
R.integer.abc_max_action_buttons);
}
if (mMenu != null) {
mMenu.onItemsChanged(true);
}
}
public void setWidthLimit(int width, boolean strict) {
mWidthLimit = width;
mStrictWidthLimit = strict;
mWidthLimitSet = true;
}
public void setReserveOverflow(boolean reserveOverflow) {
mReserveOverflow = reserveOverflow;
mReserveOverflowSet = true;
}
public void setItemLimit(int itemCount) {
mMaxItems = itemCount;
mMaxItemsSet = true;
}
public void setExpandedActionViewsExclusive(boolean isExclusive) {
mExpandedActionViewsExclusive = isExclusive;
}
@Override
public MenuView getMenuView(ViewGroup root) {
MenuView result = super.getMenuView(root);
((ActionMenuView) result).setPresenter(this);
return result;
}
@Override
public View getItemView(final MenuItemImpl item, View convertView, ViewGroup parent) {
View actionView = item.getActionView();
if (actionView == null || item.hasCollapsibleActionView()) {
actionView = super.getItemView(item, convertView, parent);
}
actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
final ActionMenuView menuParent = (ActionMenuView) parent;
final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
if (!menuParent.checkLayoutParams(lp)) {
actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
}
return actionView;
}
@Override
public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
itemView.initialize(item, 0);
final ActionMenuView menuView = (ActionMenuView) mMenuView;
final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
actionItemView.setItemInvoker(menuView);
if (mPopupCallback == null) {
mPopupCallback = new ActionMenuPopupCallback();
}
actionItemView.setPopupCallback(mPopupCallback);
}
@Override
public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
return item.isActionButton();
}
@Override
public void updateMenuView(boolean cleared) {
final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
if (menuViewParent != null) {
ActionBarTransition.beginDelayedTransition(menuViewParent);
}
super.updateMenuView(cleared);
((View) mMenuView).requestLayout();
if (mMenu != null) {
final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
final int count = actionItems.size();
for (int i = 0; i < count; i++) {
final ActionProvider provider = actionItems.get(i).getSupportActionProvider();
if (provider != null) {
provider.setSubUiVisibilityListener(this);
}
}
}
final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
mMenu.getNonActionItems() : null;
boolean hasOverflow = false;
if (mReserveOverflow && nonActionItems != null) {
final int count = nonActionItems.size();
if (count == 1) {
hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
} else {
hasOverflow = count > 0;
}
}
if (hasOverflow) {
if (mOverflowButton == null) {
mOverflowButton = new OverflowMenuButton(mSystemContext);
}
ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
if (parent != mMenuView) {
if (parent != null) {
parent.removeView(mOverflowButton);
}
ActionMenuView menuView = (ActionMenuView) mMenuView;
menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
}
} else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
((ViewGroup) mMenuView).removeView(mOverflowButton);
}
((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
}
@Override
public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
if (parent.getChildAt(childIndex) == mOverflowButton) return false;
return super.filterLeftoverView(parent, childIndex);
}
public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
if (!subMenu.hasVisibleItems()) return false;
SubMenuBuilder topSubMenu = subMenu;
while (topSubMenu.getParentMenu() != mMenu) {
topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
}
View anchor = findViewForItem(topSubMenu.getItem());
if (anchor == null) {
if (mOverflowButton == null) return false;
anchor = mOverflowButton;
}
mOpenSubMenuId = subMenu.getItem().getItemId();
mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
mActionButtonPopup.setAnchorView(anchor);
mActionButtonPopup.show();
super.onSubMenuSelected(subMenu);
return true;
}
private View findViewForItem(MenuItem item) {
final ViewGroup parent = (ViewGroup) mMenuView;
if (parent == null) return null;
final int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
final View child = parent.getChildAt(i);
if (child instanceof MenuView.ItemView &&
((MenuView.ItemView) child).getItemData() == item) {
return child;
}
}
return null;
}
/**
* Display the overflow menu if one is present.
* @return true if the overflow menu was shown, false otherwise.
*/
public boolean showOverflowMenu() {
if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) {
OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
mPostedOpenRunnable = new OpenOverflowRunnable(popup);
// Post this for later; we might still need a layout for the anchor to be right.
((View) mMenuView).post(mPostedOpenRunnable);
// ActionMenuPresenter uses null as a callback argument here
// to indicate overflow is opening.
super.onSubMenuSelected(null);
return true;
}
return false;
}
/**
* Hide the overflow menu if it is currently showing.
*
* @return true if the overflow menu was hidden, false otherwise.
*/
public boolean hideOverflowMenu() {
if (mPostedOpenRunnable != null && mMenuView != null) {
((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
mPostedOpenRunnable = null;
return true;
}
MenuPopupHelper popup = mOverflowPopup;
if (popup != null) {
popup.dismiss();
return true;
}
return false;
}
/**
* Dismiss all popup menus - overflow and submenus.
* @return true if popups were dismissed, false otherwise. (This can be because none were open.)
*/
public boolean dismissPopupMenus() {
boolean result = hideOverflowMenu();
result |= hideSubMenus();
return result;
}
/**
* Dismiss all submenu popups.
*
* @return true if popups were dismissed, false otherwise. (This can be because none were open.)
*/
public boolean hideSubMenus() {
if (mActionButtonPopup != null) {
mActionButtonPopup.dismiss();
return true;
}
return false;
}
/**
* @return true if the overflow menu is currently showing
*/
public boolean isOverflowMenuShowing() {
return mOverflowPopup != null && mOverflowPopup.isShowing();
}
public boolean isOverflowMenuShowPending() {
return mPostedOpenRunnable != null || isOverflowMenuShowing();
}
/**
* @return true if space has been reserved in the action menu for an overflow item.
*/
public boolean isOverflowReserved() {
return mReserveOverflow;
}
public boolean flagActionItems() {
final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
final int itemsSize = visibleItems.size();
int maxActions = mMaxItems;
int widthLimit = mActionItemWidthLimit;
final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final ViewGroup parent = (ViewGroup) mMenuView;
int requiredItems = 0;
int requestedItems = 0;
int firstActionWidth = 0;
boolean hasOverflow = false;
for (int i = 0; i < itemsSize; i++) {
MenuItemImpl item = visibleItems.get(i);
if (item.requiresActionButton()) {
requiredItems++;
} else if (item.requestsActionButton()) {
requestedItems++;
} else {
hasOverflow = true;
}
if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
// Overflow everything if we have an expanded action view and we're
// space constrained.
maxActions = 0;
}
}
// Reserve a spot for the overflow item if needed.
if (mReserveOverflow &&
(hasOverflow || requiredItems + requestedItems > maxActions)) {
maxActions--;
}
maxActions -= requiredItems;
final SparseBooleanArray seenGroups = mActionButtonGroups;
seenGroups.clear();
int cellSize = 0;
int cellsRemaining = 0;
if (mStrictWidthLimit) {
cellsRemaining = widthLimit / mMinCellSize;
final int cellSizeRemaining = widthLimit % mMinCellSize;
cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
}
// Flag as many more requested items as will fit.
for (int i = 0; i < itemsSize; i++) {
MenuItemImpl item = visibleItems.get(i);
if (item.requiresActionButton()) {
View v = getItemView(item, mScrapActionButtonView, parent);
if (mScrapActionButtonView == null) {
mScrapActionButtonView = v;
}
if (mStrictWidthLimit) {
cellsRemaining -= ActionMenuView.measureChildForCells(v,
cellSize, cellsRemaining, querySpec, 0);
} else {
v.measure(querySpec, querySpec);
}
final int measuredWidth = v.getMeasuredWidth();
widthLimit -= measuredWidth;
if (firstActionWidth == 0) {
firstActionWidth = measuredWidth;
}
final int groupId = item.getGroupId();
if (groupId != 0) {
seenGroups.put(groupId, true);
}
item.setIsActionButton(true);
} else if (item.requestsActionButton()) {
// Items in a group with other items that already have an action slot
// can break the max actions rule, but not the width limit.
final int groupId = item.getGroupId();
final boolean inGroup = seenGroups.get(groupId);
boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
(!mStrictWidthLimit || cellsRemaining > 0);
if (isAction) {
View v = getItemView(item, mScrapActionButtonView, parent);
if (mScrapActionButtonView == null) {
mScrapActionButtonView = v;
}
if (mStrictWidthLimit) {
final int cells = ActionMenuView.measureChildForCells(v,
cellSize, cellsRemaining, querySpec, 0);
cellsRemaining -= cells;
if (cells == 0) {
isAction = false;
}
} else {
v.measure(querySpec, querySpec);
}
final int measuredWidth = v.getMeasuredWidth();
widthLimit -= measuredWidth;
if (firstActionWidth == 0) {
firstActionWidth = measuredWidth;
}
if (mStrictWidthLimit) {
isAction &= widthLimit >= 0;
} else {
// Did this push the entire first item past the limit?
isAction &= widthLimit + firstActionWidth > 0;
}
}
if (isAction && groupId != 0) {
seenGroups.put(groupId, true);
} else if (inGroup) {
// We broke the width limit. Demote the whole group, they all overflow now.
seenGroups.put(groupId, false);
for (int j = 0; j < i; j++) {
MenuItemImpl areYouMyGroupie = visibleItems.get(j);
if (areYouMyGroupie.getGroupId() == groupId) {
// Give back the action slot
if (areYouMyGroupie.isActionButton()) maxActions++;
areYouMyGroupie.setIsActionButton(false);
}
}
}
if (isAction) maxActions--;
item.setIsActionButton(isAction);
} else {
// Neither requires nor requests an action button.
item.setIsActionButton(false);
}
}
return true;
}
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
dismissPopupMenus();
super.onCloseMenu(menu, allMenusAreClosing);
}
@Override
public Parcelable onSaveInstanceState() {
SavedState state = new SavedState();
state.openSubMenuId = mOpenSubMenuId;
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState saved = (SavedState) state;
if (saved.openSubMenuId > 0) {
MenuItem item = mMenu.findItem(saved.openSubMenuId);
if (item != null) {
SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
onSubMenuSelected(subMenu);
}
}
}
@Override
public void onSubUiVisibilityChanged(boolean isVisible) {
if (isVisible) {
// Not a submenu, but treat it like one.
super.onSubMenuSelected(null);
} else {
mMenu.close(false);
}
}
public void setMenuView(ActionMenuView menuView) {
mMenuView = menuView;
menuView.initialize(mMenu);
}
private static class SavedState implements Parcelable {
public int openSubMenuId;
SavedState() {
}
SavedState(Parcel in) {
openSubMenuId = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(openSubMenuId);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
private class OverflowMenuButton extends TintImageView implements ActionMenuView.ActionMenuChildView {
private final float[] mTempPts = new float[2];
public OverflowMenuButton(Context context) {
super(context, null, R.attr.actionOverflowButtonStyle);
setClickable(true);
setFocusable(true);
setVisibility(VISIBLE);
setEnabled(true);
setOnTouchListener(new ListPopupWindow.ForwardingListener(this) {
@Override
public ListPopupWindow getPopup() {
if (mOverflowPopup == null) {
return null;
}
return mOverflowPopup.getPopup();
}
@Override
public boolean onForwardingStarted() {
showOverflowMenu();
return true;
}
@Override
public boolean onForwardingStopped() {
// Displaying the popup occurs asynchronously, so wait for
// the runnable to finish before deciding whether to stop
// forwarding.
if (mPostedOpenRunnable != null) {
return false;
}
hideOverflowMenu();
return true;
}
});
}
@Override
public boolean performClick() {
if (super.performClick()) {
return true;
}
playSoundEffect(SoundEffectConstants.CLICK);
showOverflowMenu();
return true;
}
@Override
public boolean needsDividerBefore() {
return false;
}
@Override
public boolean needsDividerAfter() {
return false;
}
@Override
protected boolean setFrame(int l, int t, int r, int b) {
final boolean changed = super.setFrame(l, t, r, b);
// Set up the hotspot bounds to be centered on the image.
final Drawable d = getDrawable();
final Drawable bg = getBackground();
if (d != null && bg != null) {
final int width = getWidth();
final int height = getHeight();
final int halfEdge = Math.max(width, height) / 2;
final int offsetX = getPaddingLeft() - getPaddingRight();
final int offsetY = getPaddingTop() - getPaddingBottom();
final int centerX = (width + offsetX) / 2;
final int centerY = (height + offsetY) / 2;
DrawableCompat.setHotspotBounds(bg, centerX - halfEdge, centerY - halfEdge,
centerX + halfEdge, centerY + halfEdge);
}
return changed;
}
}
private class OverflowPopup extends MenuPopupHelper {
public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
boolean overflowOnly) {
super(context, menu, anchorView, overflowOnly, R.attr.actionOverflowMenuStyle);
setGravity(GravityCompat.END);
setCallback(mPopupPresenterCallback);
}
@Override
public void onDismiss() {
super.onDismiss();
mMenu.close();
mOverflowPopup = null;
}
}
private class ActionButtonSubmenu extends MenuPopupHelper {
private SubMenuBuilder mSubMenu;
public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
super(context, subMenu, null, false,
R.attr.actionOverflowMenuStyle);
mSubMenu = subMenu;
MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
if (!item.isActionButton()) {
// Give a reasonable anchor to nested submenus.
setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
}
setCallback(mPopupPresenterCallback);
boolean preserveIconSpacing = false;
final int count = subMenu.size();
for (int i = 0; i < count; i++) {
MenuItem childItem = subMenu.getItem(i);
if (childItem.isVisible() && childItem.getIcon() != null) {
preserveIconSpacing = true;
break;
}
}
setForceShowIcon(preserveIconSpacing);
}
@Override
public void onDismiss() {
super.onDismiss();
mActionButtonPopup = null;
mOpenSubMenuId = 0;
}
}
private class PopupPresenterCallback implements Callback {
@Override
public boolean onOpenSubMenu(MenuBuilder subMenu) {
if (subMenu == null) return false;
mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
final Callback cb = getCallback();
return cb != null ? cb.onOpenSubMenu(subMenu) : false;
}
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
if (menu instanceof SubMenuBuilder) {
((SubMenuBuilder) menu).getRootMenu().close(false);
}
final Callback cb = getCallback();
if (cb != null) {
cb.onCloseMenu(menu, allMenusAreClosing);
}
}
}
private class OpenOverflowRunnable implements Runnable {
private OverflowPopup mPopup;
public OpenOverflowRunnable(OverflowPopup popup) {
mPopup = popup;
}
public void run() {
mMenu.changeMenuMode();
final View menuView = (View) mMenuView;
if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
mOverflowPopup = mPopup;
}
mPostedOpenRunnable = null;
}
}
private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
@Override
public ListPopupWindow getPopup() {
return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
}
}
}

View file

@ -0,0 +1,811 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 android.support.v7.widget;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.support.v7.internal.view.menu.ActionMenuItemView;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.view.menu.MenuItemImpl;
import android.support.v7.internal.view.menu.MenuPresenter;
import android.support.v7.internal.view.menu.MenuView;
import android.support.v7.internal.widget.ViewUtils;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
/**
* ActionMenuView is a presentation of a series of menu options as a View. It provides
* several top level options as action buttons while spilling remaining options over as
* items in an overflow menu. This allows applications to present packs of actions inline with
* specific or repeating content.
*/
public class ActionMenuView extends LinearLayoutCompat implements MenuBuilder.ItemInvoker,
MenuView {
private static final String TAG = "ActionMenuView";
static final int MIN_CELL_SIZE = 56; // dips
static final int GENERATED_ITEM_PADDING = 4; // dips
private MenuBuilder mMenu;
private Context mContext;
/** Context against which to inflate popup menus. */
private Context mPopupContext;
/** Theme resource against which to inflate popup menus. */
private int mPopupTheme;
private boolean mReserveOverflow;
private ActionMenuPresenter mPresenter;
private MenuPresenter.Callback mActionMenuPresenterCallback;
private MenuBuilder.Callback mMenuBuilderCallback;
private boolean mFormatItems;
private int mFormatItemsWidth;
private int mMinCellSize;
private int mGeneratedItemPadding;
private OnMenuItemClickListener mOnMenuItemClickListener;
public ActionMenuView(Context context) {
this(context, null);
}
public ActionMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setBaselineAligned(false);
final float density = context.getResources().getDisplayMetrics().density;
mMinCellSize = (int) (MIN_CELL_SIZE * density);
mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
mPopupContext = context;
mPopupTheme = 0;
}
/**
* Specifies the theme to use when inflating popup menus. By default, uses
* the same theme as the action menu view itself.
*
* @param resId theme used to inflate popup menus
* @see #getPopupTheme()
*/
public void setPopupTheme(int resId) {
if (mPopupTheme != resId) {
mPopupTheme = resId;
if (resId == 0) {
mPopupContext = mContext;
} else {
mPopupContext = new ContextThemeWrapper(mContext, resId);
}
}
}
/**
* @return resource identifier of the theme used to inflate popup menus, or
* 0 if menus are inflated against the action menu view theme
* @see #setPopupTheme(int)
*/
public int getPopupTheme() {
return mPopupTheme;
}
/**
* @param presenter Menu presenter used to display popup menu
* @hide
*/
public void setPresenter(ActionMenuPresenter presenter) {
mPresenter = presenter;
mPresenter.setMenuView(this);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (Build.VERSION.SDK_INT >= 8) {
super.onConfigurationChanged(newConfig);
}
if (mPresenter != null) {
mPresenter.updateMenuView(false);
if (mPresenter.isOverflowMenuShowing()) {
mPresenter.hideOverflowMenu();
mPresenter.showOverflowMenu();
}
}
}
public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
mOnMenuItemClickListener = listener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// If we've been given an exact size to match, apply special formatting during layout.
final boolean wasFormatted = mFormatItems;
mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
if (wasFormatted != mFormatItems) {
mFormatItemsWidth = 0; // Reset this when switching modes
}
// Special formatting can change whether items can fit as action buttons.
// Kick the menu and update presenters when this changes.
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
mFormatItemsWidth = widthSize;
mMenu.onItemsChanged(true);
}
final int childCount = getChildCount();
if (mFormatItems && childCount > 0) {
onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
} else {
// Previous measurement at exact format may have set margins - reset them.
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.leftMargin = lp.rightMargin = 0;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
// We already know the width mode is EXACTLY if we're here.
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int widthPadding = getPaddingLeft() + getPaddingRight();
final int heightPadding = getPaddingTop() + getPaddingBottom();
final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
ViewGroup.LayoutParams.WRAP_CONTENT);
widthSize -= widthPadding;
// Divide the view into cells.
final int cellCount = widthSize / mMinCellSize;
final int cellSizeRemaining = widthSize % mMinCellSize;
if (cellCount == 0) {
// Give up, nothing fits.
setMeasuredDimension(widthSize, 0);
return;
}
final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
int cellsRemaining = cellCount;
int maxChildHeight = 0;
int maxCellsUsed = 0;
int expandableItemCount = 0;
int visibleItemCount = 0;
boolean hasOverflow = false;
// This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
long smallestItemsAt = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) continue;
final boolean isGeneratedItem = child instanceof ActionMenuItemView;
visibleItemCount++;
if (isGeneratedItem) {
// Reset padding for generated menu item views; it may change below
// and views are recycled.
child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.expanded = false;
lp.extraPixels = 0;
lp.cellsUsed = 0;
lp.expandable = false;
lp.leftMargin = 0;
lp.rightMargin = 0;
lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
// Overflow always gets 1 cell. No more, no less.
final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
itemHeightSpec, heightPadding);
maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
if (lp.expandable) expandableItemCount++;
if (lp.isOverflowButton) hasOverflow = true;
cellsRemaining -= cellsUsed;
maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
if (cellsUsed == 1) smallestItemsAt |= (1 << i);
}
// When we have overflow and a single expanded (text) item, we want to try centering it
// visually in the available space even though overflow consumes some of it.
final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
// Divide space for remaining cells if we have items that can expand.
// Try distributing whole leftover cells to smaller items first.
boolean needsExpansion = false;
while (expandableItemCount > 0 && cellsRemaining > 0) {
int minCells = Integer.MAX_VALUE;
long minCellsAt = 0; // Bit locations are indices of relevant child views
int minCellsItemCount = 0;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Don't try to expand items that shouldn't.
if (!lp.expandable) continue;
// Mark indices of children that can receive an extra cell.
if (lp.cellsUsed < minCells) {
minCells = lp.cellsUsed;
minCellsAt = 1 << i;
minCellsItemCount = 1;
} else if (lp.cellsUsed == minCells) {
minCellsAt |= 1 << i;
minCellsItemCount++;
}
}
// Items that get expanded will always be in the set of smallest items when we're done.
smallestItemsAt |= minCellsAt;
if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
// We have enough cells, all minimum size items will be incremented.
minCells++;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if ((minCellsAt & (1 << i)) == 0) {
// If this item is already at our small item count, mark it for later.
if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
continue;
}
if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
// Add padding to this item such that it centers.
child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
}
lp.cellsUsed++;
lp.expanded = true;
cellsRemaining--;
}
needsExpansion = true;
}
// Divide any space left that wouldn't divide along cell boundaries
// evenly among the smallest items
final boolean singleItem = !hasOverflow && visibleItemCount == 1;
if (cellsRemaining > 0 && smallestItemsAt != 0 &&
(cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
float expandCount = Long.bitCount(smallestItemsAt);
if (!singleItem) {
// The items at the far edges may only expand by half in order to pin to either side.
if ((smallestItemsAt & 1) != 0) {
LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
if (!lp.preventEdgeOffset) expandCount -= 0.5f;
}
if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
if (!lp.preventEdgeOffset) expandCount -= 0.5f;
}
}
final int extraPixels = expandCount > 0 ?
(int) (cellsRemaining * cellSize / expandCount) : 0;
for (int i = 0; i < childCount; i++) {
if ((smallestItemsAt & (1 << i)) == 0) continue;
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (child instanceof ActionMenuItemView) {
// If this is one of our views, expand and measure at the larger size.
lp.extraPixels = extraPixels;
lp.expanded = true;
if (i == 0 && !lp.preventEdgeOffset) {
// First item gets part of its new padding pushed out of sight.
// The last item will get this implicitly from layout.
lp.leftMargin = -extraPixels / 2;
}
needsExpansion = true;
} else if (lp.isOverflowButton) {
lp.extraPixels = extraPixels;
lp.expanded = true;
lp.rightMargin = -extraPixels / 2;
needsExpansion = true;
} else {
// If we don't know what it is, give it some margins instead
// and let it center within its space. We still want to pin
// against the edges.
if (i != 0) {
lp.leftMargin = extraPixels / 2;
}
if (i != childCount - 1) {
lp.rightMargin = extraPixels / 2;
}
}
}
cellsRemaining = 0;
}
// Remeasure any items that have had extra space allocated to them.
if (needsExpansion) {
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.expanded) continue;
final int width = lp.cellsUsed * cellSize + lp.extraPixels;
child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
itemHeightSpec);
}
}
if (heightMode != MeasureSpec.EXACTLY) {
heightSize = maxChildHeight;
}
setMeasuredDimension(widthSize, heightSize);
}
/**
* Measure a child view to fit within cell-based formatting. The child's width
* will be measured to a whole multiple of cellSize.
*
* <p>Sets the expandable and cellsUsed fields of LayoutParams.
*
* @param child Child to measure
* @param cellSize Size of one cell
* @param cellsRemaining Number of cells remaining that this view can expand to fill
* @param parentHeightMeasureSpec MeasureSpec used by the parent view
* @param parentHeightPadding Padding present in the parent view
* @return Number of cells this child was measured to occupy
*/
static int measureChildForCells(View child, int cellSize, int cellsRemaining,
int parentHeightMeasureSpec, int parentHeightPadding) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
parentHeightPadding;
final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
(ActionMenuItemView) child : null;
final boolean hasText = itemView != null && itemView.hasText();
int cellsUsed = 0;
if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
final int childWidthSpec = MeasureSpec.makeMeasureSpec(
cellSize * cellsRemaining, MeasureSpec.AT_MOST);
child.measure(childWidthSpec, childHeightSpec);
final int measuredWidth = child.getMeasuredWidth();
cellsUsed = measuredWidth / cellSize;
if (measuredWidth % cellSize != 0) cellsUsed++;
if (hasText && cellsUsed < 2) cellsUsed = 2;
}
final boolean expandable = !lp.isOverflowButton && hasText;
lp.expandable = expandable;
lp.cellsUsed = cellsUsed;
final int targetWidth = cellsUsed * cellSize;
child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
childHeightSpec);
return cellsUsed;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (!mFormatItems) {
super.onLayout(changed, left, top, right, bottom);
return;
}
final int childCount = getChildCount();
final int midVertical = (bottom - top) / 2;
final int dividerWidth = getDividerWidth();
int overflowWidth = 0;
int nonOverflowWidth = 0;
int nonOverflowCount = 0;
int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
boolean hasOverflow = false;
final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
for (int i = 0; i < childCount; i++) {
final View v = getChildAt(i);
if (v.getVisibility() == GONE) {
continue;
}
LayoutParams p = (LayoutParams) v.getLayoutParams();
if (p.isOverflowButton) {
overflowWidth = v.getMeasuredWidth();
if (hasSupportDividerBeforeChildAt(i)) {
overflowWidth += dividerWidth;
}
int height = v.getMeasuredHeight();
int r;
int l;
if (isLayoutRtl) {
l = getPaddingLeft() + p.leftMargin;
r = l + overflowWidth;
} else {
r = getWidth() - getPaddingRight() - p.rightMargin;
l = r - overflowWidth;
}
int t = midVertical - (height / 2);
int b = t + height;
v.layout(l, t, r, b);
widthRemaining -= overflowWidth;
hasOverflow = true;
} else {
final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
nonOverflowWidth += size;
widthRemaining -= size;
if (hasSupportDividerBeforeChildAt(i)) {
nonOverflowWidth += dividerWidth;
}
nonOverflowCount++;
}
}
if (childCount == 1 && !hasOverflow) {
// Center a single child
final View v = getChildAt(0);
final int width = v.getMeasuredWidth();
final int height = v.getMeasuredHeight();
final int midHorizontal = (right - left) / 2;
final int l = midHorizontal - width / 2;
final int t = midVertical - height / 2;
v.layout(l, t, l + width, t + height);
return;
}
final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
if (isLayoutRtl) {
int startRight = getWidth() - getPaddingRight();
for (int i = 0; i < childCount; i++) {
final View v = getChildAt(i);
final LayoutParams lp = (LayoutParams) v.getLayoutParams();
if (v.getVisibility() == GONE || lp.isOverflowButton) {
continue;
}
startRight -= lp.rightMargin;
int width = v.getMeasuredWidth();
int height = v.getMeasuredHeight();
int t = midVertical - height / 2;
v.layout(startRight - width, t, startRight, t + height);
startRight -= width + lp.leftMargin + spacerSize;
}
} else {
int startLeft = getPaddingLeft();
for (int i = 0; i < childCount; i++) {
final View v = getChildAt(i);
final LayoutParams lp = (LayoutParams) v.getLayoutParams();
if (v.getVisibility() == GONE || lp.isOverflowButton) {
continue;
}
startLeft += lp.leftMargin;
int width = v.getMeasuredWidth();
int height = v.getMeasuredHeight();
int t = midVertical - height / 2;
v.layout(startLeft, t, startLeft + width, t + height);
startLeft += width + lp.rightMargin + spacerSize;
}
}
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
dismissPopupMenus();
}
/** @hide */
public boolean isOverflowReserved() {
return mReserveOverflow;
}
/** @hide */
public void setOverflowReserved(boolean reserveOverflow) {
mReserveOverflow = reserveOverflow;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_VERTICAL;
return params;
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
if (p != null) {
final LayoutParams result = p instanceof LayoutParams
? new LayoutParams((LayoutParams) p)
: new LayoutParams(p);
if (result.gravity <= Gravity.NO_GRAVITY) {
result.gravity = Gravity.CENTER_VERTICAL;
}
return result;
}
return generateDefaultLayoutParams();
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p != null && p instanceof LayoutParams;
}
/** @hide */
public LayoutParams generateOverflowButtonLayoutParams() {
LayoutParams result = generateDefaultLayoutParams();
result.isOverflowButton = true;
return result;
}
/** @hide */
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
/** @hide */
public int getWindowAnimations() {
return 0;
}
/** @hide */
public void initialize(MenuBuilder menu) {
mMenu = menu;
}
/**
* Returns the Menu object that this ActionMenuView is currently presenting.
*
* <p>Applications should use this method to obtain the ActionMenuView's Menu object
* and inflate or add content to it as necessary.</p>
*
* @return the Menu presented by this view
*/
public Menu getMenu() {
if (mMenu == null) {
final Context context = getContext();
mMenu = new MenuBuilder(context);
mMenu.setCallback(new MenuBuilderCallback());
mPresenter = new ActionMenuPresenter(context);
mPresenter.setReserveOverflow(true);
mPresenter.setCallback(mActionMenuPresenterCallback != null
? mActionMenuPresenterCallback : new ActionMenuPresenterCallback());
mMenu.addMenuPresenter(mPresenter, mPopupContext);
mPresenter.setMenuView(this);
}
return mMenu;
}
/**
* Must be called before the first call to getMenu()
* @hide
*/
public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
mActionMenuPresenterCallback = pcb;
mMenuBuilderCallback = mcb;
}
/**
* Returns the current menu or null if one has not yet been configured.
* @hide Internal use only for action bar integration
*/
public MenuBuilder peekMenu() {
return mMenu;
}
/**
* Show the overflow items from the associated menu.
*
* @return true if the menu was able to be shown, false otherwise
*/
public boolean showOverflowMenu() {
return mPresenter != null && mPresenter.showOverflowMenu();
}
/**
* Hide the overflow items from the associated menu.
*
* @return true if the menu was able to be hidden, false otherwise
*/
public boolean hideOverflowMenu() {
return mPresenter != null && mPresenter.hideOverflowMenu();
}
/**
* Check whether the overflow menu is currently showing. This may not reflect
* a pending show operation in progress.
*
* @return true if the overflow menu is currently showing
*/
public boolean isOverflowMenuShowing() {
return mPresenter != null && mPresenter.isOverflowMenuShowing();
}
/** @hide */
public boolean isOverflowMenuShowPending() {
return mPresenter != null && mPresenter.isOverflowMenuShowPending();
}
/**
* Dismiss any popups associated with this menu view.
*/
public void dismissPopupMenus() {
if (mPresenter != null) {
mPresenter.dismissPopupMenus();
}
}
/**
* @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
*/
protected boolean hasSupportDividerBeforeChildAt(int childIndex) {
if (childIndex == 0) {
return false;
}
final View childBefore = getChildAt(childIndex - 1);
final View child = getChildAt(childIndex);
boolean result = false;
if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
}
if (childIndex > 0 && child instanceof ActionMenuChildView) {
result |= ((ActionMenuChildView) child).needsDividerBefore();
}
return result;
}
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
return false;
}
/** @hide */
public void setExpandedActionViewsExclusive(boolean exclusive) {
mPresenter.setExpandedActionViewsExclusive(exclusive);
}
/**
* Interface responsible for receiving menu item click events if the items themselves
* do not have individual item click listeners.
*/
public interface OnMenuItemClickListener {
/**
* This method will be invoked when a menu item is clicked if the item itself did
* not already handle the event.
*
* @param item {@link MenuItem} that was clicked
* @return <code>true</code> if the event was handled, <code>false</code> otherwise.
*/
public boolean onMenuItemClick(MenuItem item);
}
private class MenuBuilderCallback implements MenuBuilder.Callback {
@Override
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
return mOnMenuItemClickListener != null &&
mOnMenuItemClickListener.onMenuItemClick(item);
}
@Override
public void onMenuModeChange(MenuBuilder menu) {
if (mMenuBuilderCallback != null) {
mMenuBuilderCallback.onMenuModeChange(menu);
}
}
}
private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback {
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
}
@Override
public boolean onOpenSubMenu(MenuBuilder subMenu) {
return false;
}
}
/** @hide */
public interface ActionMenuChildView {
public boolean needsDividerBefore();
public boolean needsDividerAfter();
}
public static class LayoutParams extends LinearLayoutCompat.LayoutParams {
@ViewDebug.ExportedProperty()
public boolean isOverflowButton;
@ViewDebug.ExportedProperty()
public int cellsUsed;
@ViewDebug.ExportedProperty()
public int extraPixels;
@ViewDebug.ExportedProperty()
public boolean expandable;
@ViewDebug.ExportedProperty()
public boolean preventEdgeOffset;
boolean expanded;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(ViewGroup.LayoutParams other) {
super(other);
}
public LayoutParams(LayoutParams other) {
super((ViewGroup.LayoutParams) other);
isOverflowButton = other.isOverflowButton;
}
public LayoutParams(int width, int height) {
super(width, height);
isOverflowButton = false;
}
LayoutParams(int width, int height, boolean isOverflowButton) {
super(width, height);
this.isOverflowButton = isOverflowButton;
}
}
}

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more