mobile/android/base/home/TopSitesGridView.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/home/TopSitesGridView.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,275 @@
     1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko.home;
    1.10 +
    1.11 +import java.util.EnumSet;
    1.12 +
    1.13 +import org.mozilla.gecko.R;
    1.14 +import org.mozilla.gecko.Telemetry;
    1.15 +import org.mozilla.gecko.TelemetryContract;
    1.16 +import org.mozilla.gecko.ThumbnailHelper;
    1.17 +import org.mozilla.gecko.db.BrowserDB.URLColumns;
    1.18 +import org.mozilla.gecko.db.TopSitesCursorWrapper;
    1.19 +import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
    1.20 +
    1.21 +import android.content.Context;
    1.22 +import android.content.res.TypedArray;
    1.23 +import android.database.Cursor;
    1.24 +import android.graphics.Rect;
    1.25 +import android.text.TextUtils;
    1.26 +import android.util.AttributeSet;
    1.27 +import android.view.ContextMenu.ContextMenuInfo;
    1.28 +import android.view.View;
    1.29 +import android.widget.AbsListView;
    1.30 +import android.widget.AdapterView;
    1.31 +import android.widget.GridView;
    1.32 +
    1.33 +/**
    1.34 + * A grid view of top and pinned sites.
    1.35 + * Each cell in the grid is a TopSitesGridItemView.
    1.36 + */
    1.37 +public class TopSitesGridView extends GridView {
    1.38 +    private static final String LOGTAG = "GeckoTopSitesGridView";
    1.39 +
    1.40 +    // Listener for editing pinned sites.
    1.41 +    public static interface OnEditPinnedSiteListener {
    1.42 +        public void onEditPinnedSite(int position, String searchTerm);
    1.43 +    }
    1.44 +
    1.45 +    // Max number of top sites that needs to be shown.
    1.46 +    private final int mMaxSites;
    1.47 +
    1.48 +    // Number of columns to show.
    1.49 +    private final int mNumColumns;
    1.50 +
    1.51 +    // Horizontal spacing in between the rows.
    1.52 +    private final int mHorizontalSpacing;
    1.53 +
    1.54 +    // Vertical spacing in between the rows.
    1.55 +    private final int mVerticalSpacing;
    1.56 +
    1.57 +    // Measured width of this view.
    1.58 +    private int mMeasuredWidth;
    1.59 +
    1.60 +    // Measured height of this view.
    1.61 +    private int mMeasuredHeight;
    1.62 +
    1.63 +    // On URL open listener.
    1.64 +    private OnUrlOpenListener mUrlOpenListener;
    1.65 +
    1.66 +    // Edit pinned site listener.
    1.67 +    private OnEditPinnedSiteListener mEditPinnedSiteListener;
    1.68 +
    1.69 +    // Context menu info.
    1.70 +    private TopSitesGridContextMenuInfo mContextMenuInfo;
    1.71 +
    1.72 +    // Whether we're handling focus changes or not. This is used
    1.73 +    // to avoid infinite re-layouts when using this GridView as
    1.74 +    // a ListView header view (see bug 918044).
    1.75 +    private boolean mIsHandlingFocusChange;
    1.76 +
    1.77 +    public TopSitesGridView(Context context) {
    1.78 +        this(context, null);
    1.79 +    }
    1.80 +
    1.81 +    public TopSitesGridView(Context context, AttributeSet attrs) {
    1.82 +        this(context, attrs, R.attr.topSitesGridViewStyle);
    1.83 +    }
    1.84 +
    1.85 +    public TopSitesGridView(Context context, AttributeSet attrs, int defStyle) {
    1.86 +        super(context, attrs, defStyle);
    1.87 +        mMaxSites = getResources().getInteger(R.integer.number_of_top_sites);
    1.88 +        mNumColumns = getResources().getInteger(R.integer.number_of_top_sites_cols);
    1.89 +        setNumColumns(mNumColumns);
    1.90 +
    1.91 +        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopSitesGridView, defStyle, 0);
    1.92 +        mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_horizontalSpacing, 0x00);
    1.93 +        mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_verticalSpacing, 0x00);
    1.94 +        a.recycle();
    1.95 +
    1.96 +        mIsHandlingFocusChange = false;
    1.97 +    }
    1.98 +
    1.99 +    /**
   1.100 +     * {@inheritDoc}
   1.101 +     */
   1.102 +    @Override
   1.103 +    public void onAttachedToWindow() {
   1.104 +        super.onAttachedToWindow();
   1.105 +
   1.106 +        setOnItemClickListener(new AdapterView.OnItemClickListener() {
   1.107 +            @Override
   1.108 +            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
   1.109 +                TopSitesGridItemView row = (TopSitesGridItemView) view;
   1.110 +
   1.111 +                // Decode "user-entered" URLs before loading them.
   1.112 +                String url = HomeFragment.decodeUserEnteredUrl(row.getUrl());
   1.113 +
   1.114 +                // If the url is empty, the user can pin a site.
   1.115 +                // If not, navigate to the page given by the url.
   1.116 +                if (!TextUtils.isEmpty(url)) {
   1.117 +                    if (mUrlOpenListener != null) {
   1.118 +                        Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.GRID_ITEM, Integer.toString(position));
   1.119 +
   1.120 +                        mUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
   1.121 +                    }
   1.122 +                } else {
   1.123 +                    if (mEditPinnedSiteListener != null) {
   1.124 +                        mEditPinnedSiteListener.onEditPinnedSite(position, "");
   1.125 +                    }
   1.126 +                }
   1.127 +            }
   1.128 +        });
   1.129 +
   1.130 +        setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
   1.131 +            @Override
   1.132 +            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
   1.133 +                Cursor cursor = (Cursor) parent.getItemAtPosition(position);
   1.134 +
   1.135 +                TopSitesGridItemView gridView = (TopSitesGridItemView) view;
   1.136 +                if (cursor == null || gridView.isEmpty()) {
   1.137 +                    mContextMenuInfo = null;
   1.138 +                    return false;
   1.139 +                }
   1.140 +
   1.141 +                mContextMenuInfo = new TopSitesGridContextMenuInfo(view, position, id);
   1.142 +                updateContextMenuFromCursor(mContextMenuInfo, cursor);
   1.143 +                return showContextMenuForChild(TopSitesGridView.this);
   1.144 +            }
   1.145 +        });
   1.146 +    }
   1.147 +
   1.148 +    @Override
   1.149 +    public void onDetachedFromWindow() {
   1.150 +        super.onDetachedFromWindow();
   1.151 +
   1.152 +        mUrlOpenListener = null;
   1.153 +        mEditPinnedSiteListener = null;
   1.154 +    }
   1.155 +
   1.156 +    @Override
   1.157 +    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
   1.158 +        mIsHandlingFocusChange = true;
   1.159 +        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
   1.160 +        mIsHandlingFocusChange = false;
   1.161 +    }
   1.162 +
   1.163 +    @Override
   1.164 +    public void requestLayout() {
   1.165 +        if (!mIsHandlingFocusChange) {
   1.166 +            super.requestLayout();
   1.167 +        }
   1.168 +    }
   1.169 +
   1.170 +    /**
   1.171 +     * {@inheritDoc}
   1.172 +     */
   1.173 +    @Override
   1.174 +    public int getColumnWidth() {
   1.175 +        // This method will be called from onMeasure() too.
   1.176 +        // It's better to use getMeasuredWidth(), as it is safe in this case.
   1.177 +        final int totalHorizontalSpacing = mNumColumns > 0 ? (mNumColumns - 1) * mHorizontalSpacing : 0;
   1.178 +        return (getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - totalHorizontalSpacing) / mNumColumns;
   1.179 +    }
   1.180 +
   1.181 +    /**
   1.182 +     * {@inheritDoc}
   1.183 +     */
   1.184 +    @Override
   1.185 +    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1.186 +        // Sets the padding for this view.
   1.187 +        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1.188 +
   1.189 +        final int measuredWidth = getMeasuredWidth();
   1.190 +        if (measuredWidth == mMeasuredWidth) {
   1.191 +            // Return the cached values as the width is the same.
   1.192 +            setMeasuredDimension(mMeasuredWidth, mMeasuredHeight);
   1.193 +            return;
   1.194 +        }
   1.195 +
   1.196 +        final int columnWidth = getColumnWidth();
   1.197 +
   1.198 +        // Get the first child from the adapter.
   1.199 +        final TopSitesGridItemView child = new TopSitesGridItemView(getContext());
   1.200 +
   1.201 +        // Set a default LayoutParams on the child, if it doesn't have one on its own.
   1.202 +        AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams();
   1.203 +        if (params == null) {
   1.204 +            params = new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT,
   1.205 +                                                  AbsListView.LayoutParams.WRAP_CONTENT);
   1.206 +            child.setLayoutParams(params);
   1.207 +        }
   1.208 +
   1.209 +        // Measure the exact width of the child, and the height based on the width.
   1.210 +        // Note: the child (and TopSitesThumbnailView) takes care of calculating its height.
   1.211 +        int childWidthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY);
   1.212 +        int childHeightSpec = MeasureSpec.makeMeasureSpec(0,  MeasureSpec.UNSPECIFIED);
   1.213 +        child.measure(childWidthSpec, childHeightSpec);
   1.214 +        final int childHeight = child.getMeasuredHeight();
   1.215 +
   1.216 +        // This is the maximum width of the contents of each child in the grid.
   1.217 +        // Use this as the target width for thumbnails.
   1.218 +        final int thumbnailWidth = child.getMeasuredWidth() - child.getPaddingLeft() - child.getPaddingRight();
   1.219 +        ThumbnailHelper.getInstance().setThumbnailWidth(thumbnailWidth);
   1.220 +
   1.221 +        // Number of rows required to show these top sites.
   1.222 +        final int rows = (int) Math.ceil((double) mMaxSites / mNumColumns);
   1.223 +        final int childrenHeight = childHeight * rows;
   1.224 +        final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0;
   1.225 +
   1.226 +        // Total height of this view.
   1.227 +        final int measuredHeight = childrenHeight + getPaddingTop() + getPaddingBottom() + totalVerticalSpacing;
   1.228 +        setMeasuredDimension(measuredWidth, measuredHeight);
   1.229 +        mMeasuredWidth = measuredWidth;
   1.230 +        mMeasuredHeight = measuredHeight;
   1.231 +    }
   1.232 +
   1.233 +    @Override
   1.234 +    public ContextMenuInfo getContextMenuInfo() {
   1.235 +        return mContextMenuInfo;
   1.236 +    }
   1.237 +
   1.238 +    /*
   1.239 +     * Update the fields of a TopSitesGridContextMenuInfo object
   1.240 +     * from a cursor.
   1.241 +     *
   1.242 +     * @param  info    context menu info object to be updated
   1.243 +     * @param  cursor  used to update the context menu info object
   1.244 +     */
   1.245 +    private void updateContextMenuFromCursor(TopSitesGridContextMenuInfo info, Cursor cursor) {
   1.246 +        info.url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
   1.247 +        info.title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
   1.248 +        info.isPinned = ((TopSitesCursorWrapper) cursor).isPinned();
   1.249 +    }
   1.250 +    /**
   1.251 +     * Set an url open listener to be used by this view.
   1.252 +     *
   1.253 +     * @param listener An url open listener for this view.
   1.254 +     */
   1.255 +    public void setOnUrlOpenListener(OnUrlOpenListener listener) {
   1.256 +        mUrlOpenListener = listener;
   1.257 +    }
   1.258 +
   1.259 +    /**
   1.260 +     * Set an edit pinned site listener to be used by this view.
   1.261 +     *
   1.262 +     * @param listener An edit pinned site listener for this view.
   1.263 +     */
   1.264 +    public void setOnEditPinnedSiteListener(final OnEditPinnedSiteListener listener) {
   1.265 +        mEditPinnedSiteListener = listener;
   1.266 +    }
   1.267 +
   1.268 +    /**
   1.269 +     * Stores information regarding the creation of the context menu for a GridView item.
   1.270 +     */
   1.271 +    public static class TopSitesGridContextMenuInfo extends HomeContextMenuInfo {
   1.272 +        public boolean isPinned = false;
   1.273 +
   1.274 +        public TopSitesGridContextMenuInfo(View targetView, int position, long id) {
   1.275 +            super(targetView, position, id);
   1.276 +        }
   1.277 +    }
   1.278 +}

mercurial