BreadCrumbView.java revision d4893b0263f751f188f62ee3d64b3c5e129c98ed
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
17package com.android.browser;
18
19import android.content.Context;
20import android.graphics.drawable.Drawable;
21import android.text.TextUtils;
22import android.util.AttributeSet;
23import android.view.Gravity;
24import android.view.View;
25import android.view.View.OnClickListener;
26import android.widget.ImageButton;
27import android.widget.ImageView;
28import android.widget.LinearLayout;
29import android.widget.TextView;
30
31import java.util.ArrayList;
32import java.util.List;
33
34/**
35 * Simple bread crumb view
36 * Use setController to receive callbacks from user interactions
37 * Use pushView, popView, clear, and getTopData to change/access the view stack
38 */
39public class BreadCrumbView extends LinearLayout implements OnClickListener {
40
41    interface Controller {
42        public void onTop(int level, Object data);
43    }
44
45    private ImageButton mBackButton;
46    private Controller mController;
47    private List<Crumb> mCrumbs;
48    private boolean mUseBackButton;
49    private Drawable mSeparatorDrawable;
50    private int mMaxVisible = -1;
51
52    /**
53     * @param context
54     * @param attrs
55     * @param defStyle
56     */
57    public BreadCrumbView(Context context, AttributeSet attrs, int defStyle) {
58        super(context, attrs, defStyle);
59        init(context);
60    }
61
62    /**
63     * @param context
64     * @param attrs
65     */
66    public BreadCrumbView(Context context, AttributeSet attrs) {
67        super(context, attrs);
68        init(context);
69    }
70
71    /**
72     * @param context
73     */
74    public BreadCrumbView(Context context) {
75        super(context);
76        init(context);
77    }
78
79    private void init(Context ctx) {
80        mUseBackButton = false;
81        mCrumbs = new ArrayList<Crumb>();
82        mSeparatorDrawable = ctx.getResources().getDrawable(
83                R.drawable.crumb_divider);
84        addBackButton();
85    }
86
87    public void setUseBackButton(boolean useflag) {
88        mUseBackButton = useflag;
89        updateVisible();
90    }
91
92    public void setController(Controller ctl) {
93        mController = ctl;
94    }
95
96    public int getMaxVisible() {
97        return mMaxVisible;
98    }
99
100    public void setMaxVisible(int max) {
101        mMaxVisible = max;
102        updateVisible();
103    }
104
105    public int getTopLevel() {
106        return mCrumbs.size();
107    }
108
109    public Object getTopData() {
110        Crumb c = getTopCrumb();
111        if (c != null) {
112            return c.data;
113        }
114        return null;
115    }
116
117    public int size() {
118        return mCrumbs.size();
119    }
120
121    public void clear() {
122        while (mCrumbs.size() > 1) {
123            pop(false);
124        }
125        pop(true);
126    }
127
128    public void notifyController() {
129        if (mController != null) {
130            if (mCrumbs.size() > 0) {
131                mController.onTop(mCrumbs.size(), getTopCrumb().data);
132            } else {
133                mController.onTop(0, null);
134            }
135        }
136    }
137
138    public void pushView(String name, Object data) {
139        pushView(name, true, data);
140    }
141
142    public void pushView(String name, boolean canGoBack, Object data) {
143        Crumb crumb = new Crumb(name, canGoBack, data);
144        pushCrumb(crumb);
145    }
146
147    public void pushView(View view, Object data) {
148        Crumb crumb = new Crumb(view, true, data);
149        pushCrumb(crumb);
150    }
151
152    public void popView() {
153        pop(true);
154    }
155
156    private void addBackButton() {
157        mBackButton = new ImageButton(mContext);
158        mBackButton.setImageResource(R.drawable.ic_back_normal);
159        mBackButton.setBackgroundResource(R.drawable.browserbarbutton);
160        mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
161                LayoutParams.MATCH_PARENT));
162        mBackButton.setOnClickListener(this);
163        mBackButton.setVisibility(View.INVISIBLE);
164        addView(mBackButton, 0);
165    }
166
167    private void pushCrumb(Crumb crumb) {
168        if (mCrumbs.size() > 0) {
169            addSeparator();
170        }
171        mCrumbs.add(crumb);
172        addView(crumb.crumbView);
173        updateVisible();
174        crumb.crumbView.setOnClickListener(this);
175    }
176
177    private void addSeparator() {
178        ImageView sep = new ImageView(mContext);
179        sep.setImageDrawable(mSeparatorDrawable);
180        sep.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
181                LayoutParams.MATCH_PARENT));
182        addView(sep);
183    }
184
185    private void pop(boolean notify) {
186        int n = mCrumbs.size();
187        if (n > 0) {
188            removeLastView();
189            if (!mUseBackButton || (n > 1)) {
190                // remove separator
191                removeLastView();
192            }
193            mCrumbs.remove(n - 1);
194            if (mUseBackButton) {
195                Crumb top = getTopCrumb();
196                if (top != null && top.canGoBack) {
197                    mBackButton.setVisibility(View.VISIBLE);
198                } else {
199                    mBackButton.setVisibility(View.INVISIBLE);
200                }
201            }
202            updateVisible();
203            if (notify) {
204                notifyController();
205            }
206        }
207    }
208
209    private void updateVisible() {
210        // start at index 1 (0 == back button)
211        int childIndex = 1;
212        if (mMaxVisible >= 0) {
213            int invisibleCrumbs = size() - mMaxVisible;
214            if (invisibleCrumbs > 0) {
215                int crumbIndex = 0;
216                while (crumbIndex < invisibleCrumbs) {
217                    // Set the crumb to GONE.
218                    getChildAt(childIndex).setVisibility(View.GONE);
219                    childIndex++;
220                    // Each crumb is followed by a separator (except the last
221                    // one).  Also make it GONE
222                    if (getChildAt(childIndex) != null) {
223                        getChildAt(childIndex).setVisibility(View.GONE);
224                    }
225                    childIndex++;
226                    // Move to the next crumb.
227                    crumbIndex++;
228                }
229            }
230            // Make sure the last two are visible.
231            int childCount = getChildCount();
232            while (childIndex < childCount) {
233                getChildAt(childIndex).setVisibility(View.VISIBLE);
234                childIndex++;
235            }
236        } else {
237            int count = getChildCount();
238            for (int i = childIndex; i < count ; i++) {
239                getChildAt(i).setVisibility(View.VISIBLE);
240            }
241        }
242        if (mUseBackButton) {
243            boolean canGoBack = getTopCrumb() != null ? getTopCrumb().canGoBack : false;
244            mBackButton.setVisibility(canGoBack ? View.VISIBLE : View.INVISIBLE);
245        } else {
246            mBackButton.setVisibility(View.GONE);
247        }
248    }
249
250    private void removeLastView() {
251        int ix = getChildCount();
252        if (ix > 0) {
253            removeViewAt(ix-1);
254        }
255    }
256
257    Crumb getTopCrumb() {
258        Crumb crumb = null;
259        if (mCrumbs.size() > 0) {
260            crumb = mCrumbs.get(mCrumbs.size() - 1);
261        }
262        return crumb;
263    }
264
265    @Override
266    public void onClick(View v) {
267        if (mBackButton == v) {
268            popView();
269            notifyController();
270        } else {
271            // pop until view matches crumb view
272            while (v != getTopCrumb().crumbView) {
273                pop(false);
274            }
275            notifyController();
276        }
277    }
278    @Override
279    public int getBaseline() {
280        int ix = getChildCount();
281        if (ix > 0) {
282            // If there is at least one crumb, the baseline will be its
283            // baseline.
284            return getChildAt(ix-1).getBaseline();
285        }
286        return super.getBaseline();
287    }
288
289    @Override
290    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
291        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
292        int height = mSeparatorDrawable.getIntrinsicHeight();
293        if (getMeasuredHeight() < height) {
294            // This should only be an issue if there are currently no separators
295            // showing; i.e. if there is one crumb and no back button.
296            int mode = View.MeasureSpec.getMode(heightMeasureSpec);
297            switch(mode) {
298                case View.MeasureSpec.AT_MOST:
299                    if (View.MeasureSpec.getSize(heightMeasureSpec) < height) {
300                        return;
301                    }
302                    break;
303                case View.MeasureSpec.EXACTLY:
304                    return;
305                default:
306                    break;
307            }
308            setMeasuredDimension(getMeasuredWidth(), height);
309        }
310    }
311
312    class Crumb {
313
314        public View crumbView;
315        public boolean canGoBack;
316        public Object data;
317
318        public Crumb(String title, boolean backEnabled, Object tag) {
319            init(makeCrumbView(title), backEnabled, tag);
320        }
321
322        public Crumb(View view, boolean backEnabled, Object tag) {
323            init(view, backEnabled, tag);
324        }
325
326        private void init(View view, boolean back, Object tag) {
327            canGoBack = back;
328            crumbView = view;
329            data = tag;
330        }
331
332        private TextView makeCrumbView(String name) {
333            TextView tv = new TextView(mContext);
334            tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
335            tv.setPadding(16, 0, 16, 0);
336            tv.setGravity(Gravity.CENTER_VERTICAL);
337            tv.setText(name);
338            tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
339                    LayoutParams.MATCH_PARENT));
340            tv.setMaxWidth(mContext.getResources().getInteger(
341                    R.integer.max_width_crumb));
342            tv.setMaxLines(1);
343            tv.setEllipsize(TextUtils.TruncateAt.END);
344            return tv;
345        }
346
347    }
348
349}
350