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         addView(mBackButton, 0);
    183     }
    184 
    185     private void pushCrumb(Crumb crumb) {
    186         if (mCrumbs.size() > 0) {
    187             addSeparator();
    188         }
    189         mCrumbs.add(crumb);
    190         addView(crumb.crumbView);
    191         updateVisible();
    192         crumb.crumbView.setOnClickListener(this);
    193     }
    194 
    195     private void addSeparator() {
    196         View sep = makeDividerView();
    197         sep.setLayoutParams(makeDividerLayoutParams());
    198         addView(sep);
    199     }
    200 
    201     private ImageView makeDividerView() {
    202         ImageView result = new ImageView(mContext);
    203         result.setImageDrawable(mSeparatorDrawable);
    204         result.setScaleType(ImageView.ScaleType.FIT_XY);
    205         return result;
    206     }
    207 
    208     private LayoutParams makeDividerLayoutParams() {
    209         LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
    210                 LayoutParams.MATCH_PARENT);
    211         params.topMargin = (int) mDividerPadding;
    212         params.bottomMargin = (int) mDividerPadding;
    213         return params;
    214     }
    215 
    216     private void pop(boolean notify) {
    217         int n = mCrumbs.size();
    218         if (n > 0) {
    219             removeLastView();
    220             if (!mUseBackButton || (n > 1)) {
    221                 // remove separator
    222                 removeLastView();
    223             }
    224             mCrumbs.remove(n - 1);
    225             if (mUseBackButton) {
    226                 Crumb top = getTopCrumb();
    227                 if (top != null && top.canGoBack) {
    228                     mBackButton.setVisibility(View.VISIBLE);
    229                 } else {
    230                     mBackButton.setVisibility(View.GONE);
    231                 }
    232             }
    233             updateVisible();
    234             if (notify) {
    235                 notifyController();
    236             }
    237         }
    238     }
    239 
    240     private void updateVisible() {
    241         // start at index 1 (0 == back button)
    242         int childIndex = 1;
    243         if (mMaxVisible >= 0) {
    244             int invisibleCrumbs = size() - mMaxVisible;
    245             if (invisibleCrumbs > 0) {
    246                 int crumbIndex = 0;
    247                 while (crumbIndex < invisibleCrumbs) {
    248                     // Set the crumb to GONE.
    249                     getChildAt(childIndex).setVisibility(View.GONE);
    250                     childIndex++;
    251                     // Each crumb is followed by a separator (except the last
    252                     // one).  Also make it GONE
    253                     if (getChildAt(childIndex) != null) {
    254                         getChildAt(childIndex).setVisibility(View.GONE);
    255                     }
    256                     childIndex++;
    257                     // Move to the next crumb.
    258                     crumbIndex++;
    259                 }
    260             }
    261             // Make sure the last two are visible.
    262             int childCount = getChildCount();
    263             while (childIndex < childCount) {
    264                 getChildAt(childIndex).setVisibility(View.VISIBLE);
    265                 childIndex++;
    266             }
    267         } else {
    268             int count = getChildCount();
    269             for (int i = childIndex; i < count ; i++) {
    270                 getChildAt(i).setVisibility(View.VISIBLE);
    271             }
    272         }
    273         if (mUseBackButton) {
    274             boolean canGoBack = getTopCrumb() != null ? getTopCrumb().canGoBack : false;
    275             mBackButton.setVisibility(canGoBack ? View.VISIBLE : View.GONE);
    276         } else {
    277             mBackButton.setVisibility(View.GONE);
    278         }
    279     }
    280 
    281     private void removeLastView() {
    282         int ix = getChildCount();
    283         if (ix > 0) {
    284             removeViewAt(ix-1);
    285         }
    286     }
    287 
    288     Crumb getTopCrumb() {
    289         Crumb crumb = null;
    290         if (mCrumbs.size() > 0) {
    291             crumb = mCrumbs.get(mCrumbs.size() - 1);
    292         }
    293         return crumb;
    294     }
    295 
    296     @Override
    297     public void onClick(View v) {
    298         if (mBackButton == v) {
    299             popView();
    300             notifyController();
    301         } else {
    302             // pop until view matches crumb view
    303             while (v != getTopCrumb().crumbView) {
    304                 pop(false);
    305             }
    306             notifyController();
    307         }
    308     }
    309     @Override
    310     public int getBaseline() {
    311         int ix = getChildCount();
    312         if (ix > 0) {
    313             // If there is at least one crumb, the baseline will be its
    314             // baseline.
    315             return getChildAt(ix-1).getBaseline();
    316         }
    317         return super.getBaseline();
    318     }
    319 
    320     @Override
    321     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    322         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    323         int height = mSeparatorDrawable.getIntrinsicHeight();
    324         if (getMeasuredHeight() < height) {
    325             // This should only be an issue if there are currently no separators
    326             // showing; i.e. if there is one crumb and no back button.
    327             int mode = View.MeasureSpec.getMode(heightMeasureSpec);
    328             switch(mode) {
    329                 case View.MeasureSpec.AT_MOST:
    330                     if (View.MeasureSpec.getSize(heightMeasureSpec) < height) {
    331                         return;
    332                     }
    333                     break;
    334                 case View.MeasureSpec.EXACTLY:
    335                     return;
    336                 default:
    337                     break;
    338             }
    339             setMeasuredDimension(getMeasuredWidth(), height);
    340         }
    341     }
    342 
    343     class Crumb {
    344 
    345         public View crumbView;
    346         public boolean canGoBack;
    347         public Object data;
    348 
    349         public Crumb(String title, boolean backEnabled, Object tag) {
    350             init(makeCrumbView(title), backEnabled, tag);
    351         }
    352 
    353         public Crumb(View view, boolean backEnabled, Object tag) {
    354             init(view, backEnabled, tag);
    355         }
    356 
    357         private void init(View view, boolean back, Object tag) {
    358             canGoBack = back;
    359             crumbView = view;
    360             data = tag;
    361         }
    362 
    363         private TextView makeCrumbView(String name) {
    364             TextView tv = new TextView(mContext);
    365             tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
    366             tv.setPadding(mCrumbPadding, 0, mCrumbPadding, 0);
    367             tv.setGravity(Gravity.CENTER_VERTICAL);
    368             tv.setText(name);
    369             tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
    370                     LayoutParams.MATCH_PARENT));
    371             tv.setSingleLine();
    372             tv.setEllipsize(TextUtils.TruncateAt.END);
    373             return tv;
    374         }
    375 
    376     }
    377 
    378 }
    379