Home | History | Annotate | Download | only in browser
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.browser;
     18 
     19 import android.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.graphics.drawable.Drawable;
     22 import android.text.TextUtils;
     23 import android.util.AttributeSet;
     24 import android.util.TypedValue;
     25 import android.view.Gravity;
     26 import android.view.View;
     27 import android.view.View.OnClickListener;
     28 import android.widget.ImageButton;
     29 import android.widget.ImageView;
     30 import android.widget.LinearLayout;
     31 import android.widget.TextView;
     32 
     33 import java.util.ArrayList;
     34 import java.util.List;
     35 
     36 /**
     37  * Simple bread crumb view
     38  * Use setController to receive callbacks from user interactions
     39  * Use pushView, popView, clear, and getTopData to change/access the view stack
     40  */
     41 public class BreadCrumbView extends LinearLayout implements OnClickListener {
     42     private static final int DIVIDER_PADDING = 12; // dips
     43     private static final int CRUMB_PADDING = 8; // dips
     44 
     45     public interface Controller {
     46         public void onTop(BreadCrumbView view, int level, Object data);
     47     }
     48 
     49     private ImageButton mBackButton;
     50     private Controller mController;
     51     private List<Crumb> mCrumbs;
     52     private boolean mUseBackButton;
     53     private Drawable mSeparatorDrawable;
     54     private float mDividerPadding;
     55     private int mMaxVisible = -1;
     56     private Context mContext;
     57     private int mCrumbPadding;
     58 
     59     /**
     60      * @param context
     61      * @param attrs
     62      * @param defStyle
     63      */
     64     public BreadCrumbView(Context context, AttributeSet attrs, int defStyle) {
     65         super(context, attrs, defStyle);
     66         init(context);
     67     }
     68 
     69     /**
     70      * @param context
     71      * @param attrs
     72      */
     73     public BreadCrumbView(Context context, AttributeSet attrs) {
     74         super(context, attrs);
     75         init(context);
     76     }
     77 
     78     /**
     79      * @param context
     80      */
     81     public BreadCrumbView(Context context) {
     82         super(context);
     83         init(context);
     84     }
     85 
     86     private void init(Context ctx) {
     87         mContext = ctx;
     88         setFocusable(true);
     89         mUseBackButton = false;
     90         mCrumbs = new ArrayList<Crumb>();
     91         TypedArray a = mContext.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
     92         mSeparatorDrawable = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
     93         a.recycle();
     94         float density = mContext.getResources().getDisplayMetrics().density;
     95         mDividerPadding = DIVIDER_PADDING * density;
     96         mCrumbPadding = (int) (CRUMB_PADDING * density);
     97         addBackButton();
     98     }
     99 
    100     public void setUseBackButton(boolean useflag) {
    101         mUseBackButton = useflag;
    102         updateVisible();
    103     }
    104 
    105     public void setController(Controller ctl) {
    106         mController = ctl;
    107     }
    108 
    109     public int getMaxVisible() {
    110         return mMaxVisible;
    111     }
    112 
    113     public void setMaxVisible(int max) {
    114         mMaxVisible = max;
    115         updateVisible();
    116     }
    117 
    118     public int getTopLevel() {
    119         return mCrumbs.size();
    120     }
    121 
    122     public Object getTopData() {
    123         Crumb c = getTopCrumb();
    124         if (c != null) {
    125             return c.data;
    126         }
    127         return null;
    128     }
    129 
    130     public int size() {
    131         return mCrumbs.size();
    132     }
    133 
    134     public void clear() {
    135         while (mCrumbs.size() > 1) {
    136             pop(false);
    137         }
    138         pop(true);
    139     }
    140 
    141     public void notifyController() {
    142         if (mController != null) {
    143             if (mCrumbs.size() > 0) {
    144                 mController.onTop(this, mCrumbs.size(), getTopCrumb().data);
    145             } else {
    146                 mController.onTop(this, 0, null);
    147             }
    148         }
    149     }
    150 
    151     public View pushView(String name, Object data) {
    152         return pushView(name, true, data);
    153     }
    154 
    155     public View pushView(String name, boolean canGoBack, Object data) {
    156         Crumb crumb = new Crumb(name, canGoBack, data);
    157         pushCrumb(crumb);
    158         return crumb.crumbView;
    159     }
    160 
    161     public void pushView(View view, Object data) {
    162         Crumb crumb = new Crumb(view, true, data);
    163         pushCrumb(crumb);
    164     }
    165 
    166     public void popView() {
    167         pop(true);
    168     }
    169 
    170     private void addBackButton() {
    171         mBackButton = new ImageButton(mContext);
    172         mBackButton.setImageResource(R.drawable.ic_back_hierarchy_holo_dark);
    173         TypedValue outValue = new TypedValue();
    174         getContext().getTheme().resolveAttribute(
    175                 android.R.attr.selectableItemBackground, outValue, true);
    176         int resid = outValue.resourceId;
    177         mBackButton.setBackgroundResource(resid);
    178         mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
    179                 LayoutParams.MATCH_PARENT));
    180         mBackButton.setOnClickListener(this);
    181         mBackButton.setVisibility(View.GONE);
    182         mBackButton.setContentDescription(mContext.getText(
    183                 R.string.accessibility_button_bookmarks_folder_up));
    184         addView(mBackButton, 0);
    185     }
    186 
    187     private void pushCrumb(Crumb crumb) {
    188         if (mCrumbs.size() > 0) {
    189             addSeparator();
    190         }
    191         mCrumbs.add(crumb);
    192         addView(crumb.crumbView);
    193         updateVisible();
    194         crumb.crumbView.setOnClickListener(this);
    195     }
    196 
    197     private void addSeparator() {
    198         View sep = makeDividerView();
    199         sep.setLayoutParams(makeDividerLayoutParams());
    200         addView(sep);
    201     }
    202 
    203     private ImageView makeDividerView() {
    204         ImageView result = new ImageView(mContext);
    205         result.setImageDrawable(mSeparatorDrawable);
    206         result.setScaleType(ImageView.ScaleType.FIT_XY);
    207         return result;
    208     }
    209 
    210     private LayoutParams makeDividerLayoutParams() {
    211         LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
    212                 LayoutParams.MATCH_PARENT);
    213         params.topMargin = (int) mDividerPadding;
    214         params.bottomMargin = (int) mDividerPadding;
    215         return params;
    216     }
    217 
    218     private void pop(boolean notify) {
    219         int n = mCrumbs.size();
    220         if (n > 0) {
    221             removeLastView();
    222             if (!mUseBackButton || (n > 1)) {
    223                 // remove separator
    224                 removeLastView();
    225             }
    226             mCrumbs.remove(n - 1);
    227             if (mUseBackButton) {
    228                 Crumb top = getTopCrumb();
    229                 if (top != null && top.canGoBack) {
    230                     mBackButton.setVisibility(View.VISIBLE);
    231                 } else {
    232                     mBackButton.setVisibility(View.GONE);
    233                 }
    234             }
    235             updateVisible();
    236             if (notify) {
    237                 notifyController();
    238             }
    239         }
    240     }
    241 
    242     private void updateVisible() {
    243         // start at index 1 (0 == back button)
    244         int childIndex = 1;
    245         if (mMaxVisible >= 0) {
    246             int invisibleCrumbs = size() - mMaxVisible;
    247             if (invisibleCrumbs > 0) {
    248                 int crumbIndex = 0;
    249                 while (crumbIndex < invisibleCrumbs) {
    250                     // Set the crumb to GONE.
    251                     getChildAt(childIndex).setVisibility(View.GONE);
    252                     childIndex++;
    253                     // Each crumb is followed by a separator (except the last
    254                     // one).  Also make it GONE
    255                     if (getChildAt(childIndex) != null) {
    256                         getChildAt(childIndex).setVisibility(View.GONE);
    257                     }
    258                     childIndex++;
    259                     // Move to the next crumb.
    260                     crumbIndex++;
    261                 }
    262             }
    263             // Make sure the last two are visible.
    264             int childCount = getChildCount();
    265             while (childIndex < childCount) {
    266                 getChildAt(childIndex).setVisibility(View.VISIBLE);
    267                 childIndex++;
    268             }
    269         } else {
    270             int count = getChildCount();
    271             for (int i = childIndex; i < count ; i++) {
    272                 getChildAt(i).setVisibility(View.VISIBLE);
    273             }
    274         }
    275         if (mUseBackButton) {
    276             boolean canGoBack = getTopCrumb() != null ? getTopCrumb().canGoBack : false;
    277             mBackButton.setVisibility(canGoBack ? View.VISIBLE : View.GONE);
    278         } else {
    279             mBackButton.setVisibility(View.GONE);
    280         }
    281     }
    282 
    283     private void removeLastView() {
    284         int ix = getChildCount();
    285         if (ix > 0) {
    286             removeViewAt(ix-1);
    287         }
    288     }
    289 
    290     Crumb getTopCrumb() {
    291         Crumb crumb = null;
    292         if (mCrumbs.size() > 0) {
    293             crumb = mCrumbs.get(mCrumbs.size() - 1);
    294         }
    295         return crumb;
    296     }
    297 
    298     @Override
    299     public void onClick(View v) {
    300         if (mBackButton == v) {
    301             popView();
    302             notifyController();
    303         } else {
    304             // pop until view matches crumb view
    305             while (v != getTopCrumb().crumbView) {
    306                 pop(false);
    307             }
    308             notifyController();
    309         }
    310     }
    311     @Override
    312     public int getBaseline() {
    313         int ix = getChildCount();
    314         if (ix > 0) {
    315             // If there is at least one crumb, the baseline will be its
    316             // baseline.
    317             return getChildAt(ix-1).getBaseline();
    318         }
    319         return super.getBaseline();
    320     }
    321 
    322     @Override
    323     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    324         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    325         int height = mSeparatorDrawable.getIntrinsicHeight();
    326         if (getMeasuredHeight() < height) {
    327             // This should only be an issue if there are currently no separators
    328             // showing; i.e. if there is one crumb and no back button.
    329             int mode = View.MeasureSpec.getMode(heightMeasureSpec);
    330             switch(mode) {
    331                 case View.MeasureSpec.AT_MOST:
    332                     if (View.MeasureSpec.getSize(heightMeasureSpec) < height) {
    333                         return;
    334                     }
    335                     break;
    336                 case View.MeasureSpec.EXACTLY:
    337                     return;
    338                 default:
    339                     break;
    340             }
    341             setMeasuredDimension(getMeasuredWidth(), height);
    342         }
    343     }
    344 
    345     class Crumb {
    346 
    347         public View crumbView;
    348         public boolean canGoBack;
    349         public Object data;
    350 
    351         public Crumb(String title, boolean backEnabled, Object tag) {
    352             init(makeCrumbView(title), backEnabled, tag);
    353         }
    354 
    355         public Crumb(View view, boolean backEnabled, Object tag) {
    356             init(view, backEnabled, tag);
    357         }
    358 
    359         private void init(View view, boolean back, Object tag) {
    360             canGoBack = back;
    361             crumbView = view;
    362             data = tag;
    363         }
    364 
    365         private TextView makeCrumbView(String name) {
    366             TextView tv = new TextView(mContext);
    367             tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
    368             tv.setPadding(mCrumbPadding, 0, mCrumbPadding, 0);
    369             tv.setGravity(Gravity.CENTER_VERTICAL);
    370             tv.setText(name);
    371             tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
    372                     LayoutParams.MATCH_PARENT));
    373             tv.setSingleLine();
    374             tv.setEllipsize(TextUtils.TruncateAt.END);
    375             return tv;
    376         }
    377 
    378     }
    379 
    380 }
    381