| /* |
| * 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); |
| mBackButton.setContentDescription(mContext.getText( |
| R.string.accessibility_button_bookmarks_folder_up)); |
| 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; |
| } |
| |
| } |
| |
| } |