/* * 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.android.browser; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import java.util.ArrayList; import java.util.List; /** * Simple bread crumb view * Use setController to receive callbacks from user interactions * Use pushView, popView, clear, and getTopData to change/access the view stack */ public class BreadCrumbView extends LinearLayout implements OnClickListener { private static final int DIVIDER_PADDING = 12; // dips private static final int CRUMB_PADDING = 8; // dips public interface Controller { public void onTop(BreadCrumbView view, int level, Object data); } private ImageButton mBackButton; private Controller mController; private List<Crumb> mCrumbs; private boolean mUseBackButton; private Drawable mSeparatorDrawable; private float mDividerPadding; private int mMaxVisible = -1; private Context mContext; private int mCrumbPadding; /** * @param context * @param attrs * @param defStyle */ public BreadCrumbView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } /** * @param context * @param attrs */ public BreadCrumbView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } /** * @param context */ public BreadCrumbView(Context context) { super(context); init(context); } private void init(Context ctx) { mContext = ctx; setFocusable(true); mUseBackButton = false; mCrumbs = new ArrayList<Crumb>(); TypedArray a = mContext.obtainStyledAttributes(com.android.internal.R.styleable.Theme); mSeparatorDrawable = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical); a.recycle(); float density = mContext.getResources().getDisplayMetrics().density; mDividerPadding = DIVIDER_PADDING * density; mCrumbPadding = (int) (CRUMB_PADDING * density); addBackButton(); } public void setUseBackButton(boolean useflag) { mUseBackButton = useflag; updateVisible(); } public void setController(Controller ctl) { mController = ctl; } public int getMaxVisible() { return mMaxVisible; } public void setMaxVisible(int max) { mMaxVisible = max; updateVisible(); } public int getTopLevel() { return mCrumbs.size(); } public Object getTopData() { Crumb c = getTopCrumb(); if (c != null) { return c.data; } return null; } public int size() { return mCrumbs.size(); } public void clear() { while (mCrumbs.size() > 1) { pop(false); } pop(true); } public void notifyController() { if (mController != null) { if (mCrumbs.size() > 0) { mController.onTop(this, mCrumbs.size(), getTopCrumb().data); } else { mController.onTop(this, 0, null); } } } public View pushView(String name, Object data) { return pushView(name, true, data); } public View pushView(String name, boolean canGoBack, Object data) { Crumb crumb = new Crumb(name, canGoBack, data); pushCrumb(crumb); return crumb.crumbView; } public void pushView(View view, Object data) { Crumb crumb = new Crumb(view, true, data); pushCrumb(crumb); } public void popView() { pop(true); } private void addBackButton() { mBackButton = new ImageButton(mContext); mBackButton.setImageResource(R.drawable.ic_back_hierarchy_holo_dark); TypedValue outValue = new TypedValue(); getContext().getTheme().resolveAttribute( android.R.attr.selectableItemBackground, outValue, true); int resid = outValue.resourceId; mBackButton.setBackgroundResource(resid); mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); mBackButton.setOnClickListener(this); mBackButton.setVisibility(View.GONE); addView(mBackButton, 0); } private void pushCrumb(Crumb crumb) { if (mCrumbs.size() > 0) { addSeparator(); } mCrumbs.add(crumb); addView(crumb.crumbView); updateVisible(); crumb.crumbView.setOnClickListener(this); } private void addSeparator() { View sep = makeDividerView(); sep.setLayoutParams(makeDividerLayoutParams()); addView(sep); } private ImageView makeDividerView() { ImageView result = new ImageView(mContext); result.setImageDrawable(mSeparatorDrawable); result.setScaleType(ImageView.ScaleType.FIT_XY); return result; } private LayoutParams makeDividerLayoutParams() { LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); params.topMargin = (int) mDividerPadding; params.bottomMargin = (int) mDividerPadding; return params; } private void pop(boolean notify) { int n = mCrumbs.size(); if (n > 0) { removeLastView(); if (!mUseBackButton || (n > 1)) { // remove separator removeLastView(); } mCrumbs.remove(n - 1); if (mUseBackButton) { Crumb top = getTopCrumb(); if (top != null && top.canGoBack) { mBackButton.setVisibility(View.VISIBLE); } else { mBackButton.setVisibility(View.GONE); } } updateVisible(); if (notify) { notifyController(); } } } private void updateVisible() { // start at index 1 (0 == back button) int childIndex = 1; if (mMaxVisible >= 0) { int invisibleCrumbs = size() - mMaxVisible; if (invisibleCrumbs > 0) { int crumbIndex = 0; while (crumbIndex < invisibleCrumbs) { // Set the crumb to GONE. getChildAt(childIndex).setVisibility(View.GONE); childIndex++; // Each crumb is followed by a separator (except the last // one). Also make it GONE if (getChildAt(childIndex) != null) { getChildAt(childIndex).setVisibility(View.GONE); } childIndex++; // Move to the next crumb. crumbIndex++; } } // Make sure the last two are visible. int childCount = getChildCount(); while (childIndex < childCount) { getChildAt(childIndex).setVisibility(View.VISIBLE); childIndex++; } } else { int count = getChildCount(); for (int i = childIndex; i < count ; i++) { getChildAt(i).setVisibility(View.VISIBLE); } } if (mUseBackButton) { boolean canGoBack = getTopCrumb() != null ? getTopCrumb().canGoBack : false; mBackButton.setVisibility(canGoBack ? View.VISIBLE : View.GONE); } else { mBackButton.setVisibility(View.GONE); } } private void removeLastView() { int ix = getChildCount(); if (ix > 0) { removeViewAt(ix-1); } } Crumb getTopCrumb() { Crumb crumb = null; if (mCrumbs.size() > 0) { crumb = mCrumbs.get(mCrumbs.size() - 1); } return crumb; } @Override public void onClick(View v) { if (mBackButton == v) { popView(); notifyController(); } else { // pop until view matches crumb view while (v != getTopCrumb().crumbView) { pop(false); } notifyController(); } } @Override public int getBaseline() { int ix = getChildCount(); if (ix > 0) { // If there is at least one crumb, the baseline will be its // baseline. return getChildAt(ix-1).getBaseline(); } return super.getBaseline(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = mSeparatorDrawable.getIntrinsicHeight(); if (getMeasuredHeight() < height) { // This should only be an issue if there are currently no separators // showing; i.e. if there is one crumb and no back button. int mode = View.MeasureSpec.getMode(heightMeasureSpec); switch(mode) { case View.MeasureSpec.AT_MOST: if (View.MeasureSpec.getSize(heightMeasureSpec) < height) { return; } break; case View.MeasureSpec.EXACTLY: return; default: break; } setMeasuredDimension(getMeasuredWidth(), height); } } class Crumb { public View crumbView; public boolean canGoBack; public Object data; public Crumb(String title, boolean backEnabled, Object tag) { init(makeCrumbView(title), backEnabled, tag); } public Crumb(View view, boolean backEnabled, Object tag) { init(view, backEnabled, tag); } private void init(View view, boolean back, Object tag) { canGoBack = back; crumbView = view; data = tag; } private TextView makeCrumbView(String name) { TextView tv = new TextView(mContext); tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium); tv.setPadding(mCrumbPadding, 0, mCrumbPadding, 0); tv.setGravity(Gravity.CENTER_VERTICAL); tv.setText(name); tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); tv.setSingleLine(); tv.setEllipsize(TextUtils.TruncateAt.END); return tv; } } }