me.futuretechnology.util.ui.AltCursorAdapter.java Source code

Java tutorial

Introduction

Here is the source code for me.futuretechnology.util.ui.AltCursorAdapter.java

Source

/*
 * Copyright (C) 2006 The Android Open Source Project
 * Copyright 2015 Eduard Scarlat
 *
 * 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 me.futuretechnology.util.ui;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

/**
 * An easy adapter to map columns from a cursor to TextViews or ImageViews defined in an XML file. You can specify which
 * columns you want, which views you want to display the columns, and the XML file that defines the appearance of these
 * views.
 * <p/>
 * Binding occurs in two phases. First, if a {@link AltCursorAdapter.ViewBinder} is available,
 * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)} is invoked. If the returned value is
 * true, binding has occured. If the returned value is false and the view to bind is a TextView,
 * {@link #setViewText(TextView, String)} is invoked. If the returned value is false and the view to bind is an
 * ImageView, {@link #setViewImage(ImageView, String)} is invoked. If no appropriate binding can be found, an
 * {@link IllegalStateException} is thrown.
 * <p/>
 * If this adapter is used with filtering, for instance in an {@link android.widget.AutoCompleteTextView}, you can use
 * the {@link AltCursorAdapter.CursorToStringConverter} and the {@link android.widget.FilterQueryProvider} interfaces to
 * get control over the filtering process. You can refer to {@link #convertToString(android.database.Cursor)} and
 * {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
 */
public class AltCursorAdapter extends CursorAdapter {
    private final Context mContext;

    private int[] mFrom; // a list of columns containing the data to bind to the UI
    private int[] mTo; // a list of view ids representing the views to which the data must be bound

    private int mStringConversionColumn = -1;
    private CursorToStringConverter mCursorToStringConverter;
    private ViewBinder mViewBinder;
    private String[] mOriginalFrom;

    private final LayoutInflater mInflater;
    private final int[] mLayouts;

    /**
     * The resources indicating what views to inflate to display the content of this array adapter in a drop down
     * widget.
     */
    private int[] mDropDownLayouts;

    /**
     * Constructor.
     *
     * @param context The context where the ListView associated with this SimpleListItemFactory is running
     * @param layouts array of resource identifiers of layout files that define the views for this list item.
     *                getViewTypeCount() will return the length of this array.
     * @param c       The database cursor. Can be null if the cursor is not available yet.
     * @param from    A list of column names representing the data to bind to the UI. Can be null if the cursor is not
     *                available yet.
     * @param to      The views that should display column in the "from" parameter. The first N views in this list are given
     *                the values of the first N columns in the from parameter. Can be null if the cursor is not available
     *                yet.
     */
    public AltCursorAdapter(Context context, int[] layouts, Cursor c, String[] from, int[] to) {
        super(context, c, 0);
        mContext = context;
        mLayouts = layouts;
        mDropDownLayouts = layouts;
        mTo = to;

        mOriginalFrom = from;

        // hack to avoid annoying Log.e messages from android.database.sqlite.SQLiteCursor
        // if (Integer.parseInt(Build.VERSION.SDK) > 7)
        // {
        for (int i = 0; i < mOriginalFrom.length; ++i) {
            int idxDot = mOriginalFrom[i].lastIndexOf('.');
            if (idxDot != -1) {
                mOriginalFrom[i] = mOriginalFrom[i].substring(idxDot + 1);
            }
        }
        // }

        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        findColumns(from);
    }

    /**
     * @see android.widget.CursorAdapter#getView(int, View, ViewGroup)
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (getCursor() == null) {
            throw new IllegalStateException("this should only be called when the cursor is valid");
        }

        if (!getCursor().moveToPosition(position)) {
            throw new IllegalStateException("couldn't move cursor to position " + position);
        }

        View v = convertView == null ? newView(mContext, getCursor(), parent) : convertView;

        bindView(v, mContext, getCursor());
        return v;
    }

    /**
     * Inflates view(s) from the specified XML file.
     *
     * @see android.widget.CursorAdapter#newView(android.content.Context, android.database.Cursor, ViewGroup)
     */
    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        int type = getItemViewType(cursor.getPosition());
        // Log.i("ACA", "getItemViewType: " + type + ", position: " + cursor.getPosition());
        return mInflater.inflate(mLayouts[type], parent, false);
    }

    /**
     * Binds all of the field names passed into the "to" parameter of the constructor with their corresponding cursor
     * columns as specified in the "from" parameter.
     * <p/>
     * Binding occurs in two phases. First, if a {@link AltCursorAdapter.ViewBinder} is available,
     * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)} is invoked. If the returned
     * value is true, binding has occurred. If the returned value is false and the view to bind is a TextView,
     * {@link #setViewText(TextView, String)} is invoked. If the returned value is false and the view to bind is an
     * ImageView, {@link #setViewImage(ImageView, String)} is invoked. If no appropriate binding can be found, an
     * {@link IllegalStateException} is thrown.
     *
     * @throws IllegalStateException if binding cannot occur
     * @see android.widget.CursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
     * @see #getViewBinder()
     * @see #setViewBinder(AltCursorAdapter.ViewBinder)
     * @see #setViewImage(ImageView, String)
     * @see #setViewText(TextView, String)
     */
    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        ViewBinder binder = mViewBinder;
        int count = mTo.length;
        int[] from = mFrom;
        int[] to = mTo;

        for (int i = 0; i < count; i++) {
            View v = view.findViewById(to[i]);
            if (v != null) {
                boolean bound = false;
                if (binder != null) {
                    bound = binder.setViewValue(v, cursor, from[i]);
                }

                if (!bound) {
                    String text = cursor.getString(from[i]);
                    if (text == null) {
                        text = "";
                    }

                    if (v instanceof TextView) {
                        setViewText((TextView) v, text);
                    } else if (v instanceof ImageView) {
                        setViewImage((ImageView) v, text);
                    } else {
                        throw new IllegalStateException(v.getClass().getName() + " is not a "
                                + " view that can be bounds by this AltCursorAdapter");
                    }
                }
            }
        }
    }

    /**
     * <p>
     * Sets the layout resources to create the drop down views.
     * </p>
     *
     * @param layouts the layout resources defining the drop down views
     * @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
     */
    public void setDropDownLayouts(int[] layouts) {
        mDropDownLayouts = layouts;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        if (getCursor() == null) {
            throw new IllegalStateException("this should only be called when the cursor is valid");
        }
        if (!getCursor().moveToPosition(position)) {
            throw new IllegalStateException("couldn't move cursor to position " + position);
        }

        View v = convertView == null ? newDropDownView(mContext, getCursor(), parent) : convertView;

        bindView(v, mContext, getCursor());
        return v;
    }

    @Override
    public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
        int type = getItemViewType(cursor.getPosition());
        // Log.i("ACA", "getItemViewType: " + type + ", position: " + cursor.getPosition());
        return mInflater.inflate(mDropDownLayouts[type], parent, false);
    }

    /**
     * Returns the {@link ViewBinder} used to bind data to views.
     *
     * @return a ViewBinder or null if the binder does not exist
     * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
     * @see #setViewBinder(AltCursorAdapter.ViewBinder)
     */
    public ViewBinder getViewBinder() {
        return mViewBinder;
    }

    /**
     * Sets the binder used to bind data to views.
     *
     * @param viewBinder the binder used to bind data to views, can be null to remove the existing binder
     * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
     * @see #getViewBinder()
     */
    public void setViewBinder(ViewBinder viewBinder) {
        mViewBinder = viewBinder;
    }

    @Override
    public int getViewTypeCount() {
        return mLayouts.length;
    }

    @Override
    public int getItemViewType(int position) {
        if (mViewBinder == null) {
            // Log.i("ACA", "binder null");
            return 0;
        }

        return mViewBinder.getItemViewType(getCursor(), position);
    }

    /**
     * Called by bindView() to set the image for an ImageView but only if there is no existing ViewBinder or if the
     * existing ViewBinder cannot handle binding to an ImageView.
     * <p/>
     * By default, the value will be treated as an image resource. If the value cannot be used as an image resource, the
     * value is used as an image Uri.
     * <p/>
     * Intended to be overridden by Adapters that need to filter strings retrieved from the database.
     *
     * @param v     ImageView to receive an image
     * @param value the value retrieved from the cursor
     */
    public void setViewImage(ImageView v, String value) {
        try {
            v.setImageResource(Integer.parseInt(value));
        } catch (NumberFormatException nfe) {
            v.setImageURI(Uri.parse(value));
        }
    }

    /**
     * Called by bindView() to set the text for a TextView but only if there is no existing ViewBinder or if the
     * existing ViewBinder cannot handle binding to an TextView.
     * <p/>
     * Intended to be overridden by Adapters that need to filter strings retrieved from the database.
     *
     * @param v    TextView to receive text
     * @param text the text to be set for the TextView
     */
    public void setViewText(TextView v, String text) {
        v.setText(text);
    }

    /**
     * Return the index of the column used to get a String representation of the Cursor.
     *
     * @return a valid index in the current Cursor or -1
     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
     * @see #setStringConversionColumn(int)
     * @see #setCursorToStringConverter(AltCursorAdapter.CursorToStringConverter)
     * @see #getCursorToStringConverter()
     */
    public int getStringConversionColumn() {
        return mStringConversionColumn;
    }

    /**
     * Defines the index of the column in the Cursor used to get a String representation of that Cursor. The column is
     * used to convert the Cursor to a String only when the current CursorToStringConverter is null.
     *
     * @param stringConversionColumn a valid index in the current Cursor or -1 to use the default conversion mechanism
     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
     * @see #getStringConversionColumn()
     * @see #setCursorToStringConverter(AltCursorAdapter.CursorToStringConverter)
     * @see #getCursorToStringConverter()
     */
    public void setStringConversionColumn(int stringConversionColumn) {
        mStringConversionColumn = stringConversionColumn;
    }

    /**
     * Returns the converter used to convert the filtering Cursor into a String.
     *
     * @return null if the converter does not exist or an instance of {@link AltCursorAdapter.CursorToStringConverter}
     * @see #setCursorToStringConverter(AltCursorAdapter.CursorToStringConverter)
     * @see #getStringConversionColumn()
     * @see #setStringConversionColumn(int)
     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
     */
    public CursorToStringConverter getCursorToStringConverter() {
        return mCursorToStringConverter;
    }

    /**
     * Sets the converter used to convert the filtering Cursor into a String.
     *
     * @param cursorToStringConverter the Cursor to String converter, or null to remove the converter
     * @see #setCursorToStringConverter(AltCursorAdapter.CursorToStringConverter)
     * @see #getStringConversionColumn()
     * @see #setStringConversionColumn(int)
     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
     */
    public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
        mCursorToStringConverter = cursorToStringConverter;
    }

    /**
     * Returns a CharSequence representation of the specified Cursor as defined by the current CursorToStringConverter.
     * If no CursorToStringConverter has been set, the String conversion column is used instead. If the conversion
     * column is -1, the returned String is empty if the cursor is null or Cursor.toString().
     *
     * @param cursor the Cursor to convert to a CharSequence
     * @return a non-null CharSequence representing the cursor
     */
    @Override
    public CharSequence convertToString(Cursor cursor) {
        if (mCursorToStringConverter != null) {
            return mCursorToStringConverter.convertToString(cursor);
        }

        if (mStringConversionColumn > -1) {
            return cursor.getString(mStringConversionColumn);
        }

        return super.convertToString(cursor);
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }

    @Override
    public long getItemId(int position) {
        if (mDataValid && mCursor != null) {
            if (mCursor.moveToPosition(position)) {
                return mCursor.getLong(mRowIDColumn);
            }

            return 0;
        }

        return 0;
    }

    /**
     * Create a map from an array of strings to an array of column-id integers in mCursor. If mCursor is null, the array
     * will be discarded.
     *
     * @param from the Strings naming the columns of interest
     */
    private void findColumns(String[] from) {
        Cursor c = getCursor();
        if (c != null) {
            int count = from.length;
            if (mFrom == null || mFrom.length != count) {
                mFrom = new int[count];
            }

            // Log.i("ACA", TextUtils.join(" ", c.getColumnNames()));

            for (int i = 0; i < count; ++i) {
                mFrom[i] = c.getColumnIndex(from[i]);
            }
        } else {
            mFrom = null;
        }
    }

    @Override
    public Cursor swapCursor(Cursor c) {
        // Log.i("ACA", c == null ? "null" : TextUtils.join(", ", c.getColumnNames()));

        Cursor res = super.swapCursor(c);
        // rescan columns in case cursor layout is different
        findColumns(mOriginalFrom);
        return res;
    }

    @Override
    public void changeCursor(Cursor c) {
        // Log.i("ACA", "changeCursor()");
        super.changeCursor(c);

        // rescan columns in case cursor layout is different
        findColumns(mOriginalFrom);
    }

    /**
     * Change the cursor and change the column-to-view mappings at the same time.
     *
     * @param c    The database cursor. Can be null if the cursor is not available yet.
     * @param from A list of column names representing the data to bind to the UI. Can be null if the cursor is not
     *             available yet.
     * @param to   The views that should display column in the "from" parameter. These should all be TextViews. The first
     *             N views in this list are given the values of the first N columns in the from parameter. Can be null if
     *             the cursor is not available yet.
     */
    public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
        // Log.i("ACA", "changeCursorAndColumns()");

        mOriginalFrom = from;
        mTo = to;
        super.changeCursor(c);

        // rescan columns in case cursor layout is different
        findColumns(mOriginalFrom);
    }

    /**
     * Change the column-to-view mappings.
     *
     * @param from A list of column names representing the data to bind to the UI.
     * @param to   The views that should display column in the "from" parameter.
     */
    public void changeColumns(String[] from, int[] to) {
        mOriginalFrom = from;
        mTo = to;
        findColumns(mOriginalFrom);
    }

    /**
     * This class can be used by external clients of AltCursorAdapter to bind values from the Cursor to views.
     * <p/>
     * You should use this class to bind values from the Cursor to views that are not directly supported by
     * AltCursorAdapter or to change the way binding occurs for views supported by AltCursorAdapter.
     *
     * @see AltCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
     * @see AltCursorAdapter#setViewImage(ImageView, String)
     * @see AltCursorAdapter#setViewText(TextView, String)
     */
    public static interface ViewBinder {
        /**
         * Binds the Cursor column defined by the specified index to the specified view.
         * <p/>
         * When binding is handled by this ViewBinder, this method must return true. If this method returns false,
         * AltCursorAdapter will attempts to handle the binding on its own.
         *
         * @param view        the view to bind the data to
         * @param cursor      the cursor to get the data from
         * @param columnIndex the column at which the data can be found in the cursor
         * @return true if the data was bound to the view, false otherwise
         */
        boolean setViewValue(View view, Cursor cursor, int columnIndex);

        int getItemViewType(Cursor cursor, int position);
    }

    /**
     * This class can be used by external clients of AltCursorAdapter to define how the Cursor should be converted to a
     * String.
     *
     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
     */
    public static interface CursorToStringConverter {
        /**
         * Returns a CharSequence representing the specified Cursor.
         *
         * @param cursor the cursor for which a CharSequence representation is requested
         * @return a non-null CharSequence representing the cursor
         */
        CharSequence convertToString(Cursor cursor);
    }
}