changed location of ActionBarSherlock lib

This commit is contained in:
Dominik
2012-11-19 18:01:05 +01:00
parent be569984b8
commit 882610627c
385 changed files with 2 additions and 1 deletions

View File

@@ -0,0 +1,291 @@
/*
* 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 com.actionbarsherlock.internal.widget;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.actionbarsherlock.R;
import com.actionbarsherlock.internal.nineoldandroids.animation.Animator;
import com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorSet;
import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator;
import com.actionbarsherlock.internal.nineoldandroids.view.NineViewGroup;
import com.actionbarsherlock.internal.view.menu.ActionMenuPresenter;
import com.actionbarsherlock.internal.view.menu.ActionMenuView;
import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getBoolean;
public abstract class AbsActionBarView extends NineViewGroup {
protected ActionMenuView mMenuView;
protected ActionMenuPresenter mActionMenuPresenter;
protected ActionBarContainer mSplitView;
protected boolean mSplitActionBar;
protected boolean mSplitWhenNarrow;
protected int mContentHeight;
final Context mContext;
protected Animator mVisibilityAnim;
protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
private static final /*Time*/Interpolator sAlphaInterpolator = new DecelerateInterpolator();
private static final int FADE_DURATION = 200;
public AbsActionBarView(Context context) {
super(context);
mContext = context;
}
public AbsActionBarView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
}
public AbsActionBarView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
}
/*
* Must be public so we can dispatch pre-2.2 via ActionBarImpl.
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
super.onConfigurationChanged(newConfig);
} else if (mMenuView != null) {
mMenuView.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.SherlockActionBar,
R.attr.actionBarStyle, 0);
setContentHeight(a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0));
a.recycle();
if (mSplitWhenNarrow) {
setSplitActionBar(getResources_getBoolean(getContext(),
R.bool.abs__split_action_bar_is_narrow));
}
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 setSplitActionBar(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(ActionBarContainer 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) {
setAlpha(0);
if (mSplitView != null && mMenuView != null) {
mMenuView.setAlpha(0);
}
}
ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1);
anim.setDuration(FADE_DURATION);
anim.setInterpolator(sAlphaInterpolator);
if (mSplitView != null && mMenuView != null) {
AnimatorSet set = new AnimatorSet();
ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, "alpha", 1);
splitAnim.setDuration(FADE_DURATION);
set.addListener(mVisAnimListener.withFinalVisibility(visibility));
set.play(anim).with(splitAnim);
set.start();
} else {
anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
anim.start();
}
} else {
ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0);
anim.setDuration(FADE_DURATION);
anim.setInterpolator(sAlphaInterpolator);
if (mSplitView != null && mMenuView != null) {
AnimatorSet set = new AnimatorSet();
ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, "alpha", 0);
splitAnim.setDuration(FADE_DURATION);
set.addListener(mVisAnimListener.withFinalVisibility(visibility));
set.play(anim).with(splitAnim);
set.start();
} else {
anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
anim.start();
}
}
}
@Override
public void setVisibility(int visibility) {
if (mVisibilityAnim != null) {
mVisibilityAnim.end();
}
super.setVisibility(visibility);
}
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 isOverflowReserved() {
return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
}
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);
}
protected int positionChild(View child, int x, int y, int contentHeight) {
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int childTop = y + (contentHeight - childHeight) / 2;
child.layout(x, childTop, x + childWidth, childTop + childHeight);
return childWidth;
}
protected int positionChildInverse(View child, int x, int y, int contentHeight) {
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int childTop = y + (contentHeight - childHeight) / 2;
child.layout(x - childWidth, childTop, x, childTop + childHeight);
return childWidth;
}
protected class VisibilityAnimListener implements Animator.AnimatorListener {
private boolean mCanceled = false;
int mFinalVisibility;
public VisibilityAnimListener withFinalVisibility(int visibility) {
mFinalVisibility = visibility;
return this;
}
@Override
public void onAnimationStart(Animator animation) {
setVisibility(VISIBLE);
mVisibilityAnim = animation;
mCanceled = false;
}
@Override
public void onAnimationEnd(Animator animation) {
if (mCanceled) return;
mVisibilityAnim = null;
setVisibility(mFinalVisibility);
if (mSplitView != null && mMenuView != null) {
mMenuView.setVisibility(mFinalVisibility);
}
}
@Override
public void onAnimationCancel(Animator animation) {
mCanceled = true;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
}

View File

@@ -0,0 +1,245 @@
/*
* 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 com.actionbarsherlock.internal.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.actionbarsherlock.R;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.internal.nineoldandroids.widget.NineFrameLayout;
/**
* 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 NineFrameLayout {
private boolean mIsTransitioning;
private View mTabContainer;
private ActionBarView mActionBarView;
private Drawable mBackground;
private Drawable mStackedBackground;
private Drawable mSplitBackground;
private boolean mIsSplit;
private boolean mIsStacked;
public ActionBarContainer(Context context) {
this(context, null);
}
public ActionBarContainer(Context context, AttributeSet attrs) {
super(context, attrs);
setBackgroundDrawable(null);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SherlockActionBar);
mBackground = a.getDrawable(R.styleable.SherlockActionBar_background);
mStackedBackground = a.getDrawable(
R.styleable.SherlockActionBar_backgroundStacked);
if (getId() == R.id.abs__split_action_bar) {
mIsSplit = true;
mSplitBackground = a.getDrawable(
R.styleable.SherlockActionBar_backgroundSplit);
}
a.recycle();
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
mActionBarView = (ActionBarView) findViewById(R.id.abs__action_bar);
}
public void setPrimaryBackground(Drawable bg) {
mBackground = bg;
invalidate();
}
public void setStackedBackground(Drawable bg) {
mStackedBackground = bg;
invalidate();
}
public void setSplitBackground(Drawable bg) {
mSplitBackground = bg;
invalidate();
}
/**
* 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;
}
@Override
public boolean onHoverEvent(MotionEvent ev) {
super.onHoverEvent(ev);
// An action bar always eats hover 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 void onDraw(Canvas canvas) {
if (getWidth() == 0 || getHeight() == 0) {
return;
}
if (mIsSplit) {
if (mSplitBackground != null) mSplitBackground.draw(canvas);
} else {
if (mBackground != null) {
mBackground.draw(canvas);
}
if (mStackedBackground != null && mIsStacked) {
mStackedBackground.draw(canvas);
}
}
}
//This causes the animation reflection to fail on pre-HC platforms
//@Override
//public android.view.ActionMode startActionModeForChild(View child, android.view.ActionMode.Callback callback) {
// // No starting an action mode for an action bar child! (Where would it go?)
// return null;
//}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mActionBarView == null) return;
final LayoutParams lp = (LayoutParams) mActionBarView.getLayoutParams();
final int actionBarViewHeight = mActionBarView.isCollapsed() ? 0 :
mActionBarView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
final int mode = MeasureSpec.getMode(heightMeasureSpec);
if (mode == MeasureSpec.AT_MOST) {
final int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(),
Math.min(actionBarViewHeight + mTabContainer.getMeasuredHeight(),
maxHeight));
}
}
}
@Override
public void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
final boolean hasTabs = mTabContainer != null && mTabContainer.getVisibility() != GONE;
if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
final int containerHeight = getMeasuredHeight();
final int tabHeight = mTabContainer.getMeasuredHeight();
if ((mActionBarView.getDisplayOptions() & ActionBar.DISPLAY_SHOW_HOME) == 0) {
// Not showing home, put tabs on top.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child == mTabContainer) continue;
if (!mActionBarView.isCollapsed()) {
child.offsetTopAndBottom(tabHeight);
}
}
mTabContainer.layout(l, 0, r, tabHeight);
} else {
mTabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
}
}
boolean needsInvalidate = false;
if (mIsSplit) {
if (mSplitBackground != null) {
mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
needsInvalidate = true;
}
} else {
if (mBackground != null) {
mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
mActionBarView.getRight(), mActionBarView.getBottom());
needsInvalidate = true;
}
if ((mIsStacked = hasTabs && mStackedBackground != null)) {
mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
mTabContainer.getRight(), mTabContainer.getBottom());
needsInvalidate = true;
}
}
if (needsInvalidate) {
invalidate();
}
}
}

View File

@@ -0,0 +1,518 @@
/*
* 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 com.actionbarsherlock.internal.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
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;
import com.actionbarsherlock.R;
import com.actionbarsherlock.internal.nineoldandroids.animation.Animator;
import com.actionbarsherlock.internal.nineoldandroids.animation.Animator.AnimatorListener;
import com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorSet;
import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator;
import com.actionbarsherlock.internal.nineoldandroids.view.animation.AnimatorProxy;
import com.actionbarsherlock.internal.nineoldandroids.widget.NineLinearLayout;
import com.actionbarsherlock.internal.view.menu.ActionMenuPresenter;
import com.actionbarsherlock.internal.view.menu.ActionMenuView;
import com.actionbarsherlock.internal.view.menu.MenuBuilder;
import com.actionbarsherlock.view.ActionMode;
/**
* @hide
*/
public class ActionBarContextView extends AbsActionBarView implements AnimatorListener {
//UNUSED private static final String TAG = "ActionBarContextView";
private CharSequence mTitle;
private CharSequence mSubtitle;
private NineLinearLayout mClose;
private View mCustomView;
private LinearLayout mTitleLayout;
private TextView mTitleView;
private TextView mSubtitleView;
private int mTitleStyleRes;
private int mSubtitleStyleRes;
private Drawable mSplitBackground;
private Animator 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);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SherlockActionMode, defStyle, 0);
setBackgroundDrawable(a.getDrawable(
R.styleable.SherlockActionMode_background));
mTitleStyleRes = a.getResourceId(
R.styleable.SherlockActionMode_titleTextStyle, 0);
mSubtitleStyleRes = a.getResourceId(
R.styleable.SherlockActionMode_subtitleTextStyle, 0);
mContentHeight = a.getLayoutDimension(
R.styleable.SherlockActionMode_height, 0);
mSplitBackground = a.getDrawable(
R.styleable.SherlockActionMode_backgroundSplit);
a.recycle();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mActionMenuPresenter != null) {
mActionMenuPresenter.hideOverflowMenu();
mActionMenuPresenter.hideSubMenus();
}
}
@Override
public void setSplitActionBar(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.setSplitActionBar(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.abs__action_bar_title_item, this);
mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1);
mTitleView = (TextView) mTitleLayout.findViewById(R.id.abs__action_bar_title);
mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.abs__action_bar_subtitle);
if (mTitleStyleRes != 0) {
mTitleView.setTextAppearance(mContext, mTitleStyleRes);
}
if (mSubtitleStyleRes != 0) {
mSubtitleView.setTextAppearance(mContext, 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(mContext);
mClose = (NineLinearLayout)inflater.inflate(R.layout.abs__action_mode_close_item, this, false);
addView(mClose);
} else if (mClose.getParent() == null) {
addView(mClose);
}
View closeButton = mClose.findViewById(R.id.abs__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(mContext);
mActionMenuPresenter.setReserveOverflow(true);
final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
if (!mSplitActionBar) {
menu.addMenuPresenter(mActionMenuPresenter);
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);
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 Animator a = mCurrentAnimation;
if (a != null) {
mCurrentAnimation = null;
a.end();
}
}
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) {
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 Animator makeInAnimation() {
mClose.setTranslationX(-mClose.getWidth() -
((MarginLayoutParams) mClose.getLayoutParams()).leftMargin);
ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 0);
buttonAnimator.setDuration(200);
buttonAnimator.addListener(this);
buttonAnimator.setInterpolator(new DecelerateInterpolator());
AnimatorSet set = new AnimatorSet();
AnimatorSet.Builder b = 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++) {
AnimatorProxy child = AnimatorProxy.wrap(mMenuView.getChildAt(i));
child.setScaleY(0);
ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0, 1);
a.setDuration(100);
a.setStartDelay(j * 70);
b.with(a);
}
}
}
return set;
}
private Animator makeOutAnimation() {
ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX",
-mClose.getWidth() - ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin);
buttonAnimator.setDuration(200);
buttonAnimator.addListener(this);
buttonAnimator.setInterpolator(new DecelerateInterpolator());
AnimatorSet set = new AnimatorSet();
AnimatorSet.Builder b = set.play(buttonAnimator);
if (mMenuView != null) {
final int count = mMenuView.getChildCount();
if (count > 0) {
for (int i = 0; i < 0; i++) {
AnimatorProxy child = AnimatorProxy.wrap(mMenuView.getChildAt(i));
child.setScaleY(0);
ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0);
a.setDuration(100);
a.setStartDelay(i * 70);
b.with(a);
}
}
}
return set;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int x = getPaddingLeft();
final int y = getPaddingTop();
final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
if (mClose != null && mClose.getVisibility() != GONE) {
MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
x += lp.leftMargin;
x += positionChild(mClose, x, y, contentHeight);
x += lp.rightMargin;
if (mAnimateInOnLayout) {
mAnimationMode = ANIMATE_IN;
mCurrentAnimation = makeInAnimation();
mCurrentAnimation.start();
mAnimateInOnLayout = false;
}
}
if (mTitleLayout != null && mCustomView == null) {
x += positionChild(mTitleLayout, x, y, contentHeight);
}
if (mCustomView != null) {
x += positionChild(mCustomView, x, y, contentHeight);
}
x = r - l - getPaddingRight();
if (mMenuView != null) {
x -= positionChildInverse(mMenuView, x, y, contentHeight);
}
}
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (mAnimationMode == ANIMATE_OUT) {
killMode();
}
mAnimationMode = ANIMATE_IDLE;
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public boolean shouldDelayChildPressedState() {
return false;
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
// Action mode started
//TODO event.setSource(this);
event.setClassName(getClass().getName());
event.setPackageName(getContext().getPackageName());
event.setContentDescription(mTitle);
} else {
//TODO super.onInitializeAccessibilityEvent(event);
}
}
}

View File

@@ -0,0 +1,40 @@
package com.actionbarsherlock.internal.widget;
import java.util.Locale;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.Button;
public class CapitalizingButton extends Button {
private static final boolean SANS_ICE_CREAM = Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH;
private static final boolean IS_GINGERBREAD = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
private static final int[] R_styleable_Button = new int[] {
android.R.attr.textAllCaps
};
private static final int R_styleable_Button_textAllCaps = 0;
private boolean mAllCaps;
public CapitalizingButton(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R_styleable_Button);
mAllCaps = a.getBoolean(R_styleable_Button_textAllCaps, true);
a.recycle();
}
public void setTextCompat(CharSequence text) {
if (SANS_ICE_CREAM && mAllCaps && text != null) {
if (IS_GINGERBREAD) {
setText(text.toString().toUpperCase(Locale.ROOT));
} else {
setText(text.toString().toUpperCase());
}
} else {
setText(text);
}
}
}

View File

@@ -0,0 +1,44 @@
package com.actionbarsherlock.internal.widget;
import java.util.Locale;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.TextView;
public class CapitalizingTextView extends TextView {
private static final boolean SANS_ICE_CREAM = Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH;
private static final boolean IS_GINGERBREAD = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
private static final int[] R_styleable_TextView = new int[] {
android.R.attr.textAllCaps
};
private static final int R_styleable_TextView_textAllCaps = 0;
private boolean mAllCaps;
public CapitalizingTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CapitalizingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R_styleable_TextView, defStyle, 0);
mAllCaps = a.getBoolean(R_styleable_TextView_textAllCaps, true);
a.recycle();
}
public void setTextCompat(CharSequence text) {
if (SANS_ICE_CREAM && mAllCaps && text != null) {
if (IS_GINGERBREAD) {
setText(text.toString().toUpperCase(Locale.ROOT));
} else {
setText(text.toString().toUpperCase());
}
} else {
setText(text);
}
}
}

View File

@@ -0,0 +1,64 @@
package com.actionbarsherlock.internal.widget;
import static android.view.View.MeasureSpec.EXACTLY;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.widget.LinearLayout;
import com.actionbarsherlock.R;
public class FakeDialogPhoneWindow extends LinearLayout {
final TypedValue mMinWidthMajor = new TypedValue();
final TypedValue mMinWidthMinor = new TypedValue();
public FakeDialogPhoneWindow(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SherlockTheme);
a.getValue(R.styleable.SherlockTheme_windowMinWidthMajor, mMinWidthMajor);
a.getValue(R.styleable.SherlockTheme_windowMinWidthMinor, mMinWidthMinor);
a.recycle();
}
/* Stolen from PhoneWindow */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
boolean measure = false;
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor;
if (tv.type != TypedValue.TYPE_NULL) {
final int min;
if (tv.type == TypedValue.TYPE_DIMENSION) {
min = (int)tv.getDimension(metrics);
} else if (tv.type == TypedValue.TYPE_FRACTION) {
min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
} else {
min = 0;
}
if (width < min) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
measure = true;
}
}
// TODO: Support height?
if (measure) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

View File

@@ -0,0 +1,479 @@
/*
* 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 com.actionbarsherlock.internal.widget;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
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.
*
* @attr ref android.R.styleable#AbsSpinner_entries
*/
public abstract class IcsAbsSpinner extends IcsAdapterView<SpinnerAdapter> {
private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
SpinnerAdapter mAdapter;
int mHeightMeasureSpec;
int mWidthMeasureSpec;
boolean mBlockLayoutRequests;
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;
public IcsAbsSpinner(Context context) {
super(context);
initAbsSpinner();
}
public IcsAbsSpinner(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IcsAbsSpinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initAbsSpinner();
/*
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.AbsSpinner, defStyle, 0);
CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
if (entries != null) {
ArrayAdapter<CharSequence> adapter =
new ArrayAdapter<CharSequence>(context,
R.layout.simple_spinner_item, entries);
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
setAdapter(adapter);
}
a.recycle();
*/
}
/**
* 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 mPaddingLeft = getPaddingLeft();
final int mPaddingTop = getPaddingTop();
final int mPaddingRight = getPaddingRight();
final int mPaddingBottom = getPaddingBottom();
mSpinnerPadding.left = mPaddingLeft > mSelectionLeftPadding ? mPaddingLeft
: mSelectionLeftPadding;
mSpinnerPadding.top = mPaddingTop > mSelectionTopPadding ? mPaddingTop
: mSelectionTopPadding;
mSpinnerPadding.right = mPaddingRight > mSelectionRightPadding ? mPaddingRight
: mSelectionRightPadding;
mSpinnerPadding.bottom = mPaddingBottom > mSelectionBottomPadding ? mPaddingBottom
: 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 != null) {
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());
if (IS_HONEYCOMB) {
heightSize = resolveSizeAndState(preferredHeight, heightMeasureSpec, 0);
widthSize = resolveSizeAndState(preferredWidth, widthMeasureSpec, 0);
} else {
heightSize = resolveSize(preferredHeight, heightMeasureSpec);
widthSize = resolveSize(preferredWidth, widthMeasureSpec);
}
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 IcsAbsSpinner.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 AbsSpinner#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private 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,272 @@
package com.actionbarsherlock.internal.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import com.actionbarsherlock.internal.nineoldandroids.widget.NineLinearLayout;
/**
* A simple extension of a regular linear layout that supports the divider API
* of Android 4.0+. The dividers are added adjacent to the children by changing
* their layout params. If you need to rely on the margins which fall in the
* same orientation as the layout you should wrap the child in a simple
* {@link android.widget.FrameLayout} so it can receive the margin.
*/
public class IcsLinearLayout extends NineLinearLayout {
private static final int[] LinearLayout = new int[] {
/* 0 */ android.R.attr.divider,
/* 1 */ android.R.attr.showDividers,
/* 2 */ android.R.attr.dividerPadding,
};
private static final int LinearLayout_divider = 0;
private static final int LinearLayout_showDividers = 1;
private static final int LinearLayout_dividerPadding = 2;
/**
* Don't show any dividers.
*/
public static final int SHOW_DIVIDER_NONE = 0;
/**
* Show a divider at the beginning of the group.
*/
public static final int SHOW_DIVIDER_BEGINNING = 1;
/**
* Show dividers between each item in the group.
*/
public static final int SHOW_DIVIDER_MIDDLE = 2;
/**
* Show a divider at the end of the group.
*/
public static final int SHOW_DIVIDER_END = 4;
private Drawable mDivider;
private int mDividerWidth;
private int mDividerHeight;
private int mShowDividers;
private int mDividerPadding;
public IcsLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/LinearLayout);
setDividerDrawable(a.getDrawable(/*com.android.internal.R.styleable.*/LinearLayout_divider));
mShowDividers = a.getInt(/*com.android.internal.R.styleable.*/LinearLayout_showDividers, SHOW_DIVIDER_NONE);
mDividerPadding = a.getDimensionPixelSize(/*com.android.internal.R.styleable.*/LinearLayout_dividerPadding, 0);
a.recycle();
}
/**
* Set how dividers should be shown between items in this layout
*
* @param showDividers One or more of {@link #SHOW_DIVIDER_BEGINNING},
* {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END},
* or {@link #SHOW_DIVIDER_NONE} to show no dividers.
*/
public void setShowDividers(int showDividers) {
if (showDividers != mShowDividers) {
requestLayout();
invalidate(); //XXX This is required if you are toggling a divider off
}
mShowDividers = showDividers;
}
/**
* @return A flag set indicating how dividers should be shown around items.
* @see #setShowDividers(int)
*/
public int getShowDividers() {
return mShowDividers;
}
/**
* Set a drawable to be used as a divider between items.
* @param divider Drawable that will divide each item.
* @see #setShowDividers(int)
*/
public void setDividerDrawable(Drawable divider) {
if (divider == mDivider) {
return;
}
mDivider = divider;
if (divider != null) {
mDividerWidth = divider.getIntrinsicWidth();
mDividerHeight = divider.getIntrinsicHeight();
} else {
mDividerWidth = 0;
mDividerHeight = 0;
}
setWillNotDraw(divider == null);
requestLayout();
}
/**
* Set padding displayed on both ends of dividers.
*
* @param padding Padding value in pixels that will be applied to each end
*
* @see #setShowDividers(int)
* @see #setDividerDrawable(Drawable)
* @see #getDividerPadding()
*/
public void setDividerPadding(int padding) {
mDividerPadding = padding;
}
/**
* Get the padding size used to inset dividers in pixels
*
* @see #setShowDividers(int)
* @see #setDividerDrawable(Drawable)
* @see #setDividerPadding(int)
*/
public int getDividerPadding() {
return mDividerPadding;
}
/**
* Get the width of the current divider drawable.
*
* @hide Used internally by framework.
*/
public int getDividerWidth() {
return mDividerWidth;
}
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
final int index = indexOfChild(child);
final int orientation = getOrientation();
final LayoutParams params = (LayoutParams) child.getLayoutParams();
if (hasDividerBeforeChildAt(index)) {
if (orientation == VERTICAL) {
//Account for the divider by pushing everything up
params.topMargin = mDividerHeight;
} else {
//Account for the divider by pushing everything left
params.leftMargin = mDividerWidth;
}
}
final int count = getChildCount();
if (index == count - 1) {
if (hasDividerBeforeChildAt(count)) {
if (orientation == VERTICAL) {
params.bottomMargin = mDividerHeight;
} else {
params.rightMargin = mDividerWidth;
}
}
}
super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
}
@Override
protected void onDraw(Canvas canvas) {
if (mDivider != null) {
if (getOrientation() == VERTICAL) {
drawDividersVertical(canvas);
} else {
drawDividersHorizontal(canvas);
}
}
super.onDraw(canvas);
}
void drawDividersVertical(Canvas canvas) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int top = child.getTop() - lp.topMargin/* - mDividerHeight*/;
drawHorizontalDivider(canvas, top);
}
}
}
if (hasDividerBeforeChildAt(count)) {
final View child = getChildAt(count - 1);
int bottom = 0;
if (child == null) {
bottom = getHeight() - getPaddingBottom() - mDividerHeight;
} else {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
bottom = child.getBottom()/* + lp.bottomMargin*/;
}
drawHorizontalDivider(canvas, bottom);
}
}
void drawDividersHorizontal(Canvas canvas) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int left = child.getLeft() - lp.leftMargin/* - mDividerWidth*/;
drawVerticalDivider(canvas, left);
}
}
}
if (hasDividerBeforeChildAt(count)) {
final View child = getChildAt(count - 1);
int right = 0;
if (child == null) {
right = getWidth() - getPaddingRight() - mDividerWidth;
} else {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
right = child.getRight()/* + lp.rightMargin*/;
}
drawVerticalDivider(canvas, right);
}
}
void drawHorizontalDivider(Canvas canvas, int top) {
mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
mDivider.draw(canvas);
}
void drawVerticalDivider(Canvas canvas, int left) {
mDivider.setBounds(left, getPaddingTop() + mDividerPadding,
left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
mDivider.draw(canvas);
}
/**
* Determines where to position dividers between children.
*
* @param childIndex Index of child to check for preceding divider
* @return true if there should be a divider before the child at childIndex
* @hide Pending API consideration. Currently only used internally by the system.
*/
protected boolean hasDividerBeforeChildAt(int childIndex) {
if (childIndex == 0) {
return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
} else if (childIndex == getChildCount()) {
return (mShowDividers & SHOW_DIVIDER_END) != 0;
} else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) {
boolean hasVisibleViewBefore = false;
for (int i = childIndex - 1; i >= 0; i--) {
if (getChildAt(i).getVisibility() != GONE) {
hasVisibleViewBefore = true;
break;
}
}
return hasVisibleViewBefore;
}
return false;
}
}

View File

@@ -0,0 +1,644 @@
package com.actionbarsherlock.internal.widget;
import com.actionbarsherlock.R;
import android.content.Context;
import android.content.res.Resources;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.PopupWindow;
/**
* A proxy between pre- and post-Honeycomb implementations of this class.
*/
public class IcsListPopupWindow {
/**
* This value controls the length of time that the user
* must leave a pointer down without scrolling to expand
* the autocomplete dropdown list to cover the IME.
*/
private static final int EXPAND_LIST_TIMEOUT = 250;
private Context mContext;
private PopupWindow mPopup;
private ListAdapter mAdapter;
private DropDownListView mDropDownList;
private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
private int mDropDownHorizontalOffset;
private int mDropDownVerticalOffset;
private boolean mDropDownVerticalOffsetSet;
private int mListItemExpandMaximum = Integer.MAX_VALUE;
private View mPromptView;
private int mPromptPosition = POSITION_PROMPT_ABOVE;
private DataSetObserver mObserver;
private View mDropDownAnchorView;
private Drawable mDropDownListHighlight;
private AdapterView.OnItemClickListener mItemClickListener;
private AdapterView.OnItemSelectedListener mItemSelectedListener;
private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
private final PopupScrollListener mScrollListener = new PopupScrollListener();
private final ListSelectorHider mHideSelector = new ListSelectorHider();
private Handler mHandler = new Handler();
private Rect mTempRect = new Rect();
private boolean mModal;
public static final int POSITION_PROMPT_ABOVE = 0;
public static final int POSITION_PROMPT_BELOW = 1;
public IcsListPopupWindow(Context context) {
this(context, null, R.attr.listPopupWindowStyle);
}
public IcsListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
mContext = context;
mPopup = new PopupWindow(context, attrs, defStyleAttr);
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
}
public IcsListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
Context wrapped = new ContextThemeWrapper(context, defStyleRes);
mPopup = new PopupWindow(wrapped, attrs, defStyleAttr);
} else {
mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes);
}
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
}
public void setAdapter(ListAdapter adapter) {
if (mObserver == null) {
mObserver = new PopupDataSetObserver();
} else if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mObserver);
}
mAdapter = adapter;
if (mAdapter != null) {
adapter.registerDataSetObserver(mObserver);
}
if (mDropDownList != null) {
mDropDownList.setAdapter(mAdapter);
}
}
public void setPromptPosition(int position) {
mPromptPosition = position;
}
public void setModal(boolean modal) {
mModal = true;
mPopup.setFocusable(modal);
}
public void setBackgroundDrawable(Drawable d) {
mPopup.setBackgroundDrawable(d);
}
public void setAnchorView(View anchor) {
mDropDownAnchorView = anchor;
}
public void setHorizontalOffset(int offset) {
mDropDownHorizontalOffset = offset;
}
public void setVerticalOffset(int offset) {
mDropDownVerticalOffset = offset;
mDropDownVerticalOffsetSet = true;
}
public void setContentWidth(int width) {
Drawable popupBackground = mPopup.getBackground();
if (popupBackground != null) {
popupBackground.getPadding(mTempRect);
mDropDownWidth = mTempRect.left + mTempRect.right + width;
} else {
mDropDownWidth = width;
}
}
public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) {
mItemClickListener = clickListener;
}
public void show() {
int height = buildDropDown();
int widthSpec = 0;
int heightSpec = 0;
boolean noInputMethod = isInputMethodNotNeeded();
//XXX mPopup.setAllowScrollingAnchorParent(!noInputMethod);
if (mPopup.isShowing()) {
if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
// The call to PopupWindow's update method below can accept -1 for any
// value you do not want to update.
widthSpec = -1;
} else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
widthSpec = mDropDownAnchorView.getWidth();
} else {
widthSpec = mDropDownWidth;
}
if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
// The call to PopupWindow's update method below can accept -1 for any
// value you do not want to update.
heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
if (noInputMethod) {
mPopup.setWindowLayoutMode(
mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
} else {
mPopup.setWindowLayoutMode(
mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
ViewGroup.LayoutParams.MATCH_PARENT : 0,
ViewGroup.LayoutParams.MATCH_PARENT);
}
} else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
heightSpec = height;
} else {
heightSpec = mDropDownHeight;
}
mPopup.setOutsideTouchable(true);
mPopup.update(mDropDownAnchorView, mDropDownHorizontalOffset,
mDropDownVerticalOffset, widthSpec, heightSpec);
} else {
if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
mPopup.setWidth(mDropDownAnchorView.getWidth());
} else {
mPopup.setWidth(mDropDownWidth);
}
}
if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
mPopup.setHeight(height);
} else {
mPopup.setHeight(mDropDownHeight);
}
}
mPopup.setWindowLayoutMode(widthSpec, heightSpec);
//XXX mPopup.setClipToScreenEnabled(true);
// use outside touchable to dismiss drop down when touching outside of it, so
// only set this if the dropdown is not always visible
mPopup.setOutsideTouchable(true);
mPopup.setTouchInterceptor(mTouchInterceptor);
mPopup.showAsDropDown(mDropDownAnchorView,
mDropDownHorizontalOffset, mDropDownVerticalOffset);
mDropDownList.setSelection(ListView.INVALID_POSITION);
if (!mModal || mDropDownList.isInTouchMode()) {
clearListSelection();
}
if (!mModal) {
mHandler.post(mHideSelector);
}
}
}
public void dismiss() {
mPopup.dismiss();
if (mPromptView != null) {
final ViewParent parent = mPromptView.getParent();
if (parent instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) parent;
group.removeView(mPromptView);
}
}
mPopup.setContentView(null);
mDropDownList = null;
mHandler.removeCallbacks(mResizePopupRunnable);
}
public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
mPopup.setOnDismissListener(listener);
}
public void setInputMethodMode(int mode) {
mPopup.setInputMethodMode(mode);
}
public void clearListSelection() {
final DropDownListView list = mDropDownList;
if (list != null) {
// WARNING: Please read the comment where mListSelectionHidden is declared
list.mListSelectionHidden = true;
//XXX list.hideSelector();
list.requestLayout();
}
}
public boolean isShowing() {
return mPopup.isShowing();
}
private boolean isInputMethodNotNeeded() {
return mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
}
public ListView getListView() {
return mDropDownList;
}
private int buildDropDown() {
ViewGroup dropDownView;
int otherHeights = 0;
if (mDropDownList == null) {
Context context = mContext;
mDropDownList = new DropDownListView(context, !mModal);
if (mDropDownListHighlight != null) {
mDropDownList.setSelector(mDropDownListHighlight);
}
mDropDownList.setAdapter(mAdapter);
mDropDownList.setOnItemClickListener(mItemClickListener);
mDropDownList.setFocusable(true);
mDropDownList.setFocusableInTouchMode(true);
mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
if (position != -1) {
DropDownListView dropDownList = mDropDownList;
if (dropDownList != null) {
dropDownList.mListSelectionHidden = false;
}
}
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
mDropDownList.setOnScrollListener(mScrollListener);
if (mItemSelectedListener != null) {
mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
}
dropDownView = mDropDownList;
View hintView = mPromptView;
if (hintView != null) {
// if an hint has been specified, we accomodate more space for it and
// add a text view in the drop down menu, at the bottom of the list
LinearLayout hintContainer = new LinearLayout(context);
hintContainer.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
);
switch (mPromptPosition) {
case POSITION_PROMPT_BELOW:
hintContainer.addView(dropDownView, hintParams);
hintContainer.addView(hintView);
break;
case POSITION_PROMPT_ABOVE:
hintContainer.addView(hintView);
hintContainer.addView(dropDownView, hintParams);
break;
default:
break;
}
// measure the hint's height to find how much more vertical space
// we need to add to the drop down's height
int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST);
int heightSpec = MeasureSpec.UNSPECIFIED;
hintView.measure(widthSpec, heightSpec);
hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
+ hintParams.bottomMargin;
dropDownView = hintContainer;
}
mPopup.setContentView(dropDownView);
} else {
dropDownView = (ViewGroup) mPopup.getContentView();
final View view = mPromptView;
if (view != null) {
LinearLayout.LayoutParams hintParams =
(LinearLayout.LayoutParams) view.getLayoutParams();
otherHeights = view.getMeasuredHeight() + hintParams.topMargin
+ hintParams.bottomMargin;
}
}
// getMaxAvailableHeight() subtracts the padding, so we put it back
// to get the available height for the whole window
int padding = 0;
Drawable background = mPopup.getBackground();
if (background != null) {
background.getPadding(mTempRect);
padding = mTempRect.top + mTempRect.bottom;
// If we don't have an explicit vertical offset, determine one from the window
// background so that content will line up.
if (!mDropDownVerticalOffsetSet) {
mDropDownVerticalOffset = -mTempRect.top;
}
}
// Max height available on the screen for a popup.
boolean ignoreBottomDecorations =
mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
final int maxHeight = /*mPopup.*/getMaxAvailableHeight(
mDropDownAnchorView, mDropDownVerticalOffset, ignoreBottomDecorations);
if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
return maxHeight + padding;
}
final int listContent = /*mDropDownList.*/measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
0, -1/*ListView.NO_POSITION*/, maxHeight - otherHeights, -1);
// add padding only if the list has items in it, that way we don't show
// the popup if it is not needed
if (listContent > 0) otherHeights += padding;
return listContent + otherHeights;
}
private int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
final Rect displayFrame = new Rect();
anchor.getWindowVisibleDisplayFrame(displayFrame);
final int[] anchorPos = new int[2];
anchor.getLocationOnScreen(anchorPos);
int bottomEdge = displayFrame.bottom;
if (ignoreBottomDecorations) {
Resources res = anchor.getContext().getResources();
bottomEdge = res.getDisplayMetrics().heightPixels;
}
final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
// anchorPos[1] is distance from anchor to top of screen
int returnedHeight = Math.max(distanceToBottom, distanceToTop);
if (mPopup.getBackground() != null) {
mPopup.getBackground().getPadding(mTempRect);
returnedHeight -= mTempRect.top + mTempRect.bottom;
}
return returnedHeight;
}
private int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
final int maxHeight, int disallowPartialChildPosition) {
final ListAdapter adapter = mAdapter;
if (adapter == null) {
return mDropDownList.getListPaddingTop() + mDropDownList.getListPaddingBottom();
}
// Include the padding of the list
int returnedHeight = mDropDownList.getListPaddingTop() + mDropDownList.getListPaddingBottom();
final int dividerHeight = ((mDropDownList.getDividerHeight() > 0) && mDropDownList.getDivider() != null) ? mDropDownList.getDividerHeight() : 0;
// The previous height value that was less than maxHeight and contained
// no partial children
int prevHeightWithoutPartialChild = 0;
int i;
View child;
// mItemCount - 1 since endPosition parameter is inclusive
endPosition = (endPosition == -1/*NO_POSITION*/) ? adapter.getCount() - 1 : endPosition;
for (i = startPosition; i <= endPosition; ++i) {
child = mAdapter.getView(i, null, mDropDownList);
if (mDropDownList.getCacheColorHint() != 0) {
child.setDrawingCacheBackgroundColor(mDropDownList.getCacheColorHint());
}
measureScrapChild(child, i, widthMeasureSpec);
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;
}
private void measureScrapChild(View child, int position, int widthMeasureSpec) {
ListView.LayoutParams p = (ListView.LayoutParams) child.getLayoutParams();
if (p == null) {
p = new ListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
child.setLayoutParams(p);
}
//XXX p.viewType = mAdapter.getItemViewType(position);
//XXX p.forceAdd = true;
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
mDropDownList.getPaddingLeft() + mDropDownList.getPaddingRight(), p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
private static class DropDownListView extends ListView {
/*
* WARNING: This is a workaround for a touch mode issue.
*
* Touch mode is propagated lazily to windows. This causes problems in
* the following scenario:
* - Type something in the AutoCompleteTextView and get some results
* - Move down with the d-pad to select an item in the list
* - Move up with the d-pad until the selection disappears
* - Type more text in the AutoCompleteTextView *using the soft keyboard*
* and get new results; you are now in touch mode
* - The selection comes back on the first item in the list, even though
* the list is supposed to be in touch mode
*
* Using the soft keyboard triggers the touch mode change but that change
* is propagated to our window only after the first list layout, therefore
* after the list attempts to resurrect the selection.
*
* The trick to work around this issue is to pretend the list is in touch
* mode when we know that the selection should not appear, that is when
* we know the user moved the selection away from the list.
*
* This boolean is set to true whenever we explicitly hide the list's
* selection and reset to false whenever we know the user moved the
* selection back to the list.
*
* When this boolean is true, isInTouchMode() returns true, otherwise it
* returns super.isInTouchMode().
*/
private boolean mListSelectionHidden;
private boolean mHijackFocus;
public DropDownListView(Context context, boolean hijackFocus) {
super(context, null, /*com.android.internal.*/R.attr.dropDownListViewStyle);
mHijackFocus = hijackFocus;
// TODO: Add an API to control this
setCacheColorHint(0); // Transparent, since the background drawable could be anything.
}
//XXX @Override
//View obtainView(int position, boolean[] isScrap) {
// View view = super.obtainView(position, isScrap);
// if (view instanceof TextView) {
// ((TextView) view).setHorizontallyScrolling(true);
// }
// return view;
//}
@Override
public boolean isInTouchMode() {
// WARNING: Please read the comment where mListSelectionHidden is declared
return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
}
@Override
public boolean hasWindowFocus() {
return mHijackFocus || super.hasWindowFocus();
}
@Override
public boolean isFocused() {
return mHijackFocus || super.isFocused();
}
@Override
public boolean hasFocus() {
return mHijackFocus || super.hasFocus();
}
}
private class PopupDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
if (isShowing()) {
// Resize the popup to fit new content
show();
}
}
@Override
public void onInvalidated() {
dismiss();
}
}
private class ListSelectorHider implements Runnable {
public void run() {
clearListSelection();
}
}
private class ResizePopupRunnable implements Runnable {
public void run() {
if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() &&
mDropDownList.getChildCount() <= mListItemExpandMaximum) {
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
show();
}
}
}
private class PopupTouchInterceptor implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getAction();
final int x = (int) event.getX();
final int y = (int) event.getY();
if (action == MotionEvent.ACTION_DOWN &&
mPopup != null && mPopup.isShowing() &&
(x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
} else if (action == MotionEvent.ACTION_UP) {
mHandler.removeCallbacks(mResizePopupRunnable);
}
return false;
}
}
private class PopupScrollListener implements ListView.OnScrollListener {
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
!isInputMethodNotNeeded() && mPopup.getContentView() != null) {
mHandler.removeCallbacks(mResizePopupRunnable);
mResizePopupRunnable.run();
}
}
}
}

View File

@@ -0,0 +1,703 @@
/*
* Copyright (C) 2007 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 com.actionbarsherlock.internal.widget;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import com.actionbarsherlock.R;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.SpinnerAdapter;
/**
* A view that displays one child at a time and lets the user pick among them.
* The items in the Spinner come from the {@link Adapter} associated with
* this view.
*
* <p>See the <a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Spinner
* tutorial</a>.</p>
*
* @attr ref android.R.styleable#Spinner_prompt
*/
public class IcsSpinner extends IcsAbsSpinner implements OnClickListener {
//private static final String TAG = "Spinner";
// Only measure this many items to get a decent max width.
private static final int MAX_ITEMS_MEASURED = 15;
/**
* Use a dialog window for selecting spinner options.
*/
//public static final int MODE_DIALOG = 0;
/**
* Use a dropdown anchored to the Spinner for selecting spinner options.
*/
public static final int MODE_DROPDOWN = 1;
/**
* Use the theme-supplied value to select the dropdown mode.
*/
//private static final int MODE_THEME = -1;
private SpinnerPopup mPopup;
private DropDownAdapter mTempAdapter;
int mDropDownWidth;
private int mGravity;
private boolean mDisableChildrenWhenDisabled;
private Rect mTempRect = new Rect();
public IcsSpinner(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.actionDropDownStyle);
}
/**
* Construct a new spinner with the given context's theme, the supplied attribute set,
* and default style.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
* @param defStyle The default style to apply to this view. If 0, no style
* will be applied (beyond what is included in the theme). This may
* either be an attribute resource, whose value will be retrieved
* from the current theme, or an explicit style resource.
*/
public IcsSpinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SherlockSpinner, defStyle, 0);
DropdownPopup popup = new DropdownPopup(context, attrs, defStyle);
mDropDownWidth = a.getLayoutDimension(
R.styleable.SherlockSpinner_android_dropDownWidth,
ViewGroup.LayoutParams.WRAP_CONTENT);
popup.setBackgroundDrawable(a.getDrawable(
R.styleable.SherlockSpinner_android_popupBackground));
final int verticalOffset = a.getDimensionPixelOffset(
R.styleable.SherlockSpinner_android_dropDownVerticalOffset, 0);
if (verticalOffset != 0) {
popup.setVerticalOffset(verticalOffset);
}
final int horizontalOffset = a.getDimensionPixelOffset(
R.styleable.SherlockSpinner_android_dropDownHorizontalOffset, 0);
if (horizontalOffset != 0) {
popup.setHorizontalOffset(horizontalOffset);
}
mPopup = popup;
mGravity = a.getInt(R.styleable.SherlockSpinner_android_gravity, Gravity.CENTER);
mPopup.setPromptText(a.getString(R.styleable.SherlockSpinner_android_prompt));
mDisableChildrenWhenDisabled = true;
a.recycle();
// Base constructor can call setAdapter before we initialize mPopup.
// Finish setting things up if this happened.
if (mTempAdapter != null) {
mPopup.setAdapter(mTempAdapter);
mTempAdapter = null;
}
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (mDisableChildrenWhenDisabled) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).setEnabled(enabled);
}
}
}
/**
* Describes how the selected item view is positioned. Currently only the horizontal component
* is used. The default is determined by the current theme.
*
* @param gravity See {@link android.view.Gravity}
*
* @attr ref android.R.styleable#Spinner_gravity
*/
public void setGravity(int gravity) {
if (mGravity != gravity) {
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
gravity |= Gravity.LEFT;
}
mGravity = gravity;
requestLayout();
}
}
@Override
public void setAdapter(SpinnerAdapter adapter) {
super.setAdapter(adapter);
if (mPopup != null) {
mPopup.setAdapter(new DropDownAdapter(adapter));
} else {
mTempAdapter = new DropDownAdapter(adapter);
}
}
@Override
public int getBaseline() {
View child = null;
if (getChildCount() > 0) {
child = getChildAt(0);
} else if (mAdapter != null && mAdapter.getCount() > 0) {
child = makeAndAddView(0);
mRecycler.put(0, child);
removeAllViewsInLayout();
}
if (child != null) {
final int childBaseline = child.getBaseline();
return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
} else {
return -1;
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mPopup != null && mPopup.isShowing()) {
mPopup.dismiss();
}
}
/**
* <p>A spinner does not support item click events. Calling this method
* will raise an exception.</p>
*
* @param l this listener will be ignored
*/
@Override
public void setOnItemClickListener(OnItemClickListener l) {
throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
final int measuredWidth = getMeasuredWidth();
setMeasuredDimension(Math.min(Math.max(measuredWidth,
measureContentWidth(getAdapter(), getBackground())),
MeasureSpec.getSize(widthMeasureSpec)),
getMeasuredHeight());
}
}
/**
* @see android.view.View#onLayout(boolean,int,int,int,int)
*
* Creates and positions all views
*
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
layout(0, false);
mInLayout = false;
}
/**
* Creates and positions all views for this Spinner.
*
* @param delta Change in the selected position. +1 moves selection is moving to the right,
* so views are scrolling to the left. -1 means selection is moving to the left.
*/
@Override
void layout(int delta, boolean animate) {
int childrenLeft = mSpinnerPadding.left;
int childrenWidth = getRight() - getLeft() - mSpinnerPadding.left - mSpinnerPadding.right;
if (mDataChanged) {
handleDataChanged();
}
// Handle the empty set by removing all views
if (mItemCount == 0) {
resetList();
return;
}
if (mNextSelectedPosition >= 0) {
setSelectedPositionInt(mNextSelectedPosition);
}
recycleAllViews();
// Clear out old views
removeAllViewsInLayout();
// Make selected view and position it
mFirstPosition = mSelectedPosition;
View sel = makeAndAddView(mSelectedPosition);
int width = sel.getMeasuredWidth();
int selectedOffset = childrenLeft;
switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
break;
case Gravity.RIGHT:
selectedOffset = childrenLeft + childrenWidth - width;
break;
}
sel.offsetLeftAndRight(selectedOffset);
// Flush any cached views that did not get reused above
mRecycler.clear();
invalidate();
checkSelectionChanged();
mDataChanged = false;
mNeedSync = false;
setNextSelectedPositionInt(mSelectedPosition);
}
/**
* Obtain a view, either by pulling an existing view from the recycler or
* by getting a new one from the adapter. If we are animating, make sure
* there is enough information in the view's layout parameters to animate
* from the old to new positions.
*
* @param position Position in the spinner for the view to obtain
* @return A view that has been added to the spinner
*/
private View makeAndAddView(int position) {
View child;
if (!mDataChanged) {
child = mRecycler.get(position);
if (child != null) {
// Position the view
setUpChild(child);
return child;
}
}
// Nothing found in the recycler -- ask the adapter for a view
child = mAdapter.getView(position, null, this);
// Position the view
setUpChild(child);
return child;
}
/**
* Helper for makeAndAddView to set the position of a view
* and fill out its layout paramters.
*
* @param child The view to position
*/
private void setUpChild(View child) {
// Respect layout params that are already in the view. Otherwise
// make some up...
ViewGroup.LayoutParams lp = child.getLayoutParams();
if (lp == null) {
lp = generateDefaultLayoutParams();
}
addViewInLayout(child, 0, lp);
child.setSelected(hasFocus());
if (mDisableChildrenWhenDisabled) {
child.setEnabled(isEnabled());
}
// Get measure specs
int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
// Measure child
child.measure(childWidthSpec, childHeightSpec);
int childLeft;
int childRight;
// Position vertically based on gravity setting
int childTop = mSpinnerPadding.top
+ ((getMeasuredHeight() - mSpinnerPadding.bottom -
mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
int childBottom = childTop + child.getMeasuredHeight();
int width = child.getMeasuredWidth();
childLeft = 0;
childRight = childLeft + width;
child.layout(childLeft, childTop, childRight, childBottom);
}
@Override
public boolean performClick() {
boolean handled = super.performClick();
if (!handled) {
handled = true;
if (!mPopup.isShowing()) {
mPopup.show();
}
}
return handled;
}
public void onClick(DialogInterface dialog, int which) {
setSelection(which);
dialog.dismiss();
}
/**
* Sets the prompt to display when the dialog is shown.
* @param prompt the prompt to set
*/
public void setPrompt(CharSequence prompt) {
mPopup.setPromptText(prompt);
}
/**
* Sets the prompt to display when the dialog is shown.
* @param promptId the resource ID of the prompt to display when the dialog is shown
*/
public void setPromptId(int promptId) {
setPrompt(getContext().getText(promptId));
}
/**
* @return The prompt to display when the dialog is shown
*/
public CharSequence getPrompt() {
return mPopup.getHintText();
}
int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
if (adapter == null) {
return 0;
}
int width = 0;
View itemView = null;
int itemType = 0;
final int widthMeasureSpec =
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final int heightMeasureSpec =
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
// Make sure the number of items we'll measure is capped. If it's a huge data set
// with wildly varying sizes, oh well.
int start = Math.max(0, getSelectedItemPosition());
final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
final int count = end - start;
start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
for (int i = start; i < end; i++) {
final int positionType = adapter.getItemViewType(i);
if (positionType != itemType) {
itemType = positionType;
itemView = null;
}
itemView = adapter.getView(i, itemView, this);
if (itemView.getLayoutParams() == null) {
itemView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
}
itemView.measure(widthMeasureSpec, heightMeasureSpec);
width = Math.max(width, itemView.getMeasuredWidth());
}
// Add background padding to measured width
if (background != null) {
background.getPadding(mTempRect);
width += mTempRect.left + mTempRect.right;
}
return width;
}
/**
* <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
* into a ListAdapter.</p>
*/
private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
private SpinnerAdapter mAdapter;
private ListAdapter mListAdapter;
/**
* <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
*
* @param adapter the Adapter to transform into a ListAdapter
*/
public DropDownAdapter(SpinnerAdapter adapter) {
this.mAdapter = adapter;
if (adapter instanceof ListAdapter) {
this.mListAdapter = (ListAdapter) adapter;
}
}
public int getCount() {
return mAdapter == null ? 0 : mAdapter.getCount();
}
public Object getItem(int position) {
return mAdapter == null ? null : mAdapter.getItem(position);
}
public long getItemId(int position) {
return mAdapter == null ? -1 : mAdapter.getItemId(position);
}
public View getView(int position, View convertView, ViewGroup parent) {
return getDropDownView(position, convertView, parent);
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return mAdapter == null ? null :
mAdapter.getDropDownView(position, convertView, parent);
}
public boolean hasStableIds() {
return mAdapter != null && mAdapter.hasStableIds();
}
public void registerDataSetObserver(DataSetObserver observer) {
if (mAdapter != null) {
mAdapter.registerDataSetObserver(observer);
}
}
public void unregisterDataSetObserver(DataSetObserver observer) {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(observer);
}
}
/**
* If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
* Otherwise, return true.
*/
public boolean areAllItemsEnabled() {
final ListAdapter adapter = mListAdapter;
if (adapter != null) {
return adapter.areAllItemsEnabled();
} else {
return true;
}
}
/**
* If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
* Otherwise, return true.
*/
public boolean isEnabled(int position) {
final ListAdapter adapter = mListAdapter;
if (adapter != null) {
return adapter.isEnabled(position);
} else {
return true;
}
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
}
}
/**
* Implements some sort of popup selection interface for selecting a spinner option.
* Allows for different spinner modes.
*/
private interface SpinnerPopup {
public void setAdapter(ListAdapter adapter);
/**
* Show the popup
*/
public void show();
/**
* Dismiss the popup
*/
public void dismiss();
/**
* @return true if the popup is showing, false otherwise.
*/
public boolean isShowing();
/**
* Set hint text to be displayed to the user. This should provide
* a description of the choice being made.
* @param hintText Hint text to set.
*/
public void setPromptText(CharSequence hintText);
public CharSequence getHintText();
}
/*
private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
private AlertDialog mPopup;
private ListAdapter mListAdapter;
private CharSequence mPrompt;
public void dismiss() {
mPopup.dismiss();
mPopup = null;
}
public boolean isShowing() {
return mPopup != null ? mPopup.isShowing() : false;
}
public void setAdapter(ListAdapter adapter) {
mListAdapter = adapter;
}
public void setPromptText(CharSequence hintText) {
mPrompt = hintText;
}
public CharSequence getHintText() {
return mPrompt;
}
public void show() {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
if (mPrompt != null) {
builder.setTitle(mPrompt);
}
mPopup = builder.setSingleChoiceItems(mListAdapter,
getSelectedItemPosition(), this).show();
}
public void onClick(DialogInterface dialog, int which) {
setSelection(which);
dismiss();
}
}
*/
private class DropdownPopup extends IcsListPopupWindow implements SpinnerPopup {
private CharSequence mHintText;
private ListAdapter mAdapter;
public DropdownPopup(Context context, AttributeSet attrs, int defStyleRes) {
super(context, attrs, 0, defStyleRes);
setAnchorView(IcsSpinner.this);
setModal(true);
setPromptPosition(POSITION_PROMPT_ABOVE);
setOnItemClickListener(new OnItemClickListener() {
@SuppressWarnings("rawtypes")
public void onItemClick(AdapterView parent, View v, int position, long id) {
IcsSpinner.this.setSelection(position);
dismiss();
}
});
}
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
mAdapter = adapter;
}
public CharSequence getHintText() {
return mHintText;
}
public void setPromptText(CharSequence hintText) {
// Hint text is ignored for dropdowns, but maintain it here.
mHintText = hintText;
}
@Override
public void show() {
final int spinnerPaddingLeft = IcsSpinner.this.getPaddingLeft();
if (mDropDownWidth == WRAP_CONTENT) {
final int spinnerWidth = IcsSpinner.this.getWidth();
final int spinnerPaddingRight = IcsSpinner.this.getPaddingRight();
setContentWidth(Math.max(
measureContentWidth((SpinnerAdapter) mAdapter, getBackground()),
spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
} else if (mDropDownWidth == MATCH_PARENT) {
final int spinnerWidth = IcsSpinner.this.getWidth();
final int spinnerPaddingRight = IcsSpinner.this.getPaddingRight();
setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
} else {
setContentWidth(mDropDownWidth);
}
final Drawable background = getBackground();
int bgOffset = 0;
if (background != null) {
background.getPadding(mTempRect);
bgOffset = -mTempRect.left;
}
setHorizontalOffset(bgOffset + spinnerPaddingLeft);
setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
super.show();
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
setSelection(IcsSpinner.this.getSelectedItemPosition());
}
}
}

View File

@@ -0,0 +1,21 @@
package com.actionbarsherlock.internal.widget;
import android.view.View;
final class IcsView {
//No instances
private IcsView() {}
/**
* Return only the state bits of {@link #getMeasuredWidthAndState()}
* and {@link #getMeasuredHeightAndState()}, combined into one integer.
* The width component is in the regular bits {@link #MEASURED_STATE_MASK}
* and the height component is at the shifted bits
* {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
*/
public static int getMeasuredStateInt(View child) {
return (child.getMeasuredWidth()&View.MEASURED_STATE_MASK)
| ((child.getMeasuredHeight()>>View.MEASURED_HEIGHT_STATE_SHIFT)
& (View.MEASURED_STATE_MASK>>View.MEASURED_HEIGHT_STATE_SHIFT));
}
}

View File

@@ -0,0 +1,545 @@
/*
* 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 com.actionbarsherlock.internal.widget;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import com.actionbarsherlock.R;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.internal.nineoldandroids.animation.Animator;
import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator;
import com.actionbarsherlock.internal.nineoldandroids.widget.NineHorizontalScrollView;
/**
* This widget implements the dynamic action bar tab behavior that can change
* across different configurations or circumstances.
*/
public class ScrollingTabContainerView extends NineHorizontalScrollView
implements IcsAdapterView.OnItemSelectedListener {
//UNUSED private static final String TAG = "ScrollingTabContainerView";
Runnable mTabSelector;
private TabClickListener mTabClickListener;
private IcsLinearLayout mTabLayout;
private IcsSpinner mTabSpinner;
private boolean mAllowCollapse;
private LayoutInflater mInflater;
int mMaxTabWidth;
private int mContentHeight;
private int mSelectedTabIndex;
protected Animator mVisibilityAnim;
protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
private static final /*Time*/Interpolator sAlphaInterpolator = new DecelerateInterpolator();
private static final int FADE_DURATION = 200;
public ScrollingTabContainerView(Context context) {
super(context);
setHorizontalScrollBarEnabled(false);
TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.SherlockActionBar,
R.attr.actionBarStyle, 0);
setContentHeight(a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0));
a.recycle();
mInflater = LayoutInflater.from(context);
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;
}
} 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);
}
}
}
public void setContentHeight(int contentHeight) {
mContentHeight = contentHeight;
requestLayout();
}
private IcsLinearLayout createTabLayout() {
final IcsLinearLayout tabLayout = (IcsLinearLayout) LayoutInflater.from(getContext())
.inflate(R.layout.abs__action_bar_tab_bar_view, null);
tabLayout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
return tabLayout;
}
private IcsSpinner createSpinner() {
final IcsSpinner spinner = new IcsSpinner(getContext(), null,
R.attr.actionDropDownStyle);
spinner.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
spinner.setOnItemSelectedListener(this);
return spinner;
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
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.SherlockActionBar,
R.attr.actionBarStyle, 0);
setContentHeight(a.getLayoutDimension(R.styleable.SherlockActionBar_height, 0));
a.recycle();
}
public void animateToVisibility(int visibility) {
if (mVisibilityAnim != null) {
mVisibilityAnim.cancel();
}
if (visibility == VISIBLE) {
if (getVisibility() != VISIBLE) {
setAlpha(0);
}
ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1);
anim.setDuration(FADE_DURATION);
anim.setInterpolator(sAlphaInterpolator);
anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
anim.start();
} else {
ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0);
anim.setDuration(FADE_DURATION);
anim.setInterpolator(sAlphaInterpolator);
anim.addListener(mVisAnimListener.withFinalVisibility(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) {
//Workaround for not being able to pass a defStyle on pre-3.0
final TabView tabView = (TabView)mInflater.inflate(R.layout.abs__action_bar_tab, null);
tabView.init(this, 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 IcsLinearLayout.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 IcsLinearLayout.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 onItemSelected(IcsAdapterView<?> parent, View view, int position, long id) {
TabView tabView = (TabView) view;
tabView.getTab().select();
}
@Override
public void onNothingSelected(IcsAdapterView<?> parent) {
}
public static class TabView extends LinearLayout {
private ScrollingTabContainerView mParent;
private ActionBar.Tab mTab;
private CapitalizingTextView mTextView;
private ImageView mIconView;
private View mCustomView;
public TabView(Context context, AttributeSet attrs) {
//TODO super(context, null, R.attr.actionBarTabStyle);
super(context, attrs);
}
public void init(ScrollingTabContainerView parent, ActionBar.Tab tab, boolean forList) {
mParent = parent;
mTab = tab;
if (forList) {
setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
}
update();
}
public void bindTab(ActionBar.Tab tab) {
mTab = tab;
update();
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Re-measure if we went beyond our maximum size.
if (mParent.mMaxTabWidth > 0 && getMeasuredWidth() > mParent.mMaxTabWidth) {
super.onMeasure(MeasureSpec.makeMeasureSpec(mParent.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);
}
if (text != null) {
if (mTextView == null) {
CapitalizingTextView textView = new CapitalizingTextView(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.setTextCompat(text);
mTextView.setVisibility(VISIBLE);
} else if (mTextView != null) {
mTextView.setVisibility(GONE);
mTextView.setText(null);
}
if (mIconView != null) {
mIconView.setContentDescription(tab.getContentDescription());
}
}
}
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 Animator.AnimatorListener {
private boolean mCanceled = false;
private int mFinalVisibility;
public VisibilityAnimListener withFinalVisibility(int visibility) {
mFinalVisibility = visibility;
return this;
}
@Override
public void onAnimationStart(Animator animation) {
setVisibility(VISIBLE);
mVisibilityAnim = animation;
mCanceled = false;
}
@Override
public void onAnimationEnd(Animator animation) {
if (mCanceled) return;
mVisibilityAnim = null;
setVisibility(mFinalVisibility);
}
@Override
public void onAnimationCancel(Animator animation) {
mCanceled = true;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
}