ContentProvider+ExpandableListView的结合

本文详细介绍了一款菜单App如何通过ContentProvider访问和操作后台数据的过程。包括安装含ContentProvider的菜单App,利用ExpandableListView展示分组数据,实现长按删除及新增菜品功能,以及数据的实时更新。

任务描述

1. 安装已提供的含ContentProvider的菜单app(menudemo.apk)

2. 在自己的程序中访问ContentProvider(authorities是com.imooc.menuprovider,获取菜单数据,模拟手风琴效果进行布局(如图)

3.长按子项,弹出提示框 询问是否确定删除,选择【是】则删除,菜品能及时更新,否则不操作点击新增,则进入新增页面,在次页面选择类型,输入菜名,点击保存按钮,则进行菜品保存,回到主页,菜品能即使更新。

【备注】在往不同分组中添加菜品信息如,如果使用的是Spinner控件实现下拉框效果

步骤:

  1. 首先新建布局文件(ExpandableListView)的父item和子item和主布局
  2. 利用ContentResolver对象获取ContentProvider的数据Data
  3. 将数据Data绑定到ExpandableListView上显示
  4. 长按子item可以删除,并实时通知适配器更新数据
  5. 可以通过ContentResolver对象对数据进行添加.

步骤1:

新建布局文件主布局:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ExpandableListView
        android:id="@+id/id_expandable_lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ExpandableListView>

</LinearLayout>

父item布局:activity_parent_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="56dp"
    android:gravity="center_vertical"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/id_parent_item_iv"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginStart="10dp"
        tools:background="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/id_parent_item_tv"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:layout_marginStart="10dp"
        android:gravity="center_vertical"
        android:textSize="20dp"
        android:textStyle="bold"
        tools:text="我是父item" />
</LinearLayout>

子item布局:activity_child_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/id_child_item_tv"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_marginStart="20dp"
        android:gravity="center_vertical"
        android:textSize="16dp"
        android:textStyle="bold"
        tools:text="我是子item" />
</LinearLayout>

父布局的选择器:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/indicator_expand" android:state_selected="true"/>
    <item android:drawable="@drawable/indicator_collapse"/>
</selector>

步骤2:拿数据

bean类封装菜品的属性

TypeList.java  * 这是父item(菜品类型)的属性封装类


import java.util.ArrayList;
import java.util.List;

/**
 * 这是父item(菜品类型)的属性封装类
 */
public class TypeList {
    //常量
    public final static String TYPE_ID = "type_id";//数据库里面的列名
    public final static String TYPE_NAME = "type_name";//数据库里面的列名

    private int id;//菜品类型id
    private String typename;//菜品类型名称
    private List<DishList> children = new ArrayList();//菜品对象

    public TypeList() {
    }

    public TypeList(int id, String typename) {
        this.id = id;
        this.typename = typename;
    }

    //添加子item的方法1
    public void addChild(DishList dishList) {
        dishList.setType(getTypename());
        children.add(dishList);
    }

    //添加子item的方法2
    public void addChild(int Childid, String Childname) {
        DishList child = new DishList(Childid, Childname);
        child.setType(getTypename());
        children.add(child);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTypename() {
        return typename;
    }

    public void setTypename(String typename) {
        this.typename = typename;
    }

    public List<DishList> getChildren() {
        return children;
    }

    public void setChildren(List<DishList> children) {
        this.children = children;
    }

    @Override
    public String toString() {
        return "TypeList{" +
                "id=" + id +
                ", typename='" + typename + '\'' +
                ", children=" + children +
                '}';
    }
}
DishList.java* 这个是子item(菜品)数据和属性的封装

/**
 * 这个是子item(菜品)数据和属性的封装
 */
public class DishList {
    //常量
    public final static String DISH_ID="dish_id";//数据库里面的列名
    public final static String DISH_NAME="dish_name";//数据库里面的列名
    public final static String DISH_TYPE="dish_type";//数据库里面的列名

    private int id;//菜品id
    private String name;//菜品名称
    private String type;//菜品所属类型

    public DishList() {
    }

    public DishList(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return "DishList{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", type='" + type + '\'' +
                '}';
    }
}

Dao类从ContentProvider拿数据,装到List中返回:

loadDataDao.java

/**
 * 这个类实现从ContentProvider中获得数据并封装返回
 */
public class loadDataDao extends AppCompatActivity {
    private static final String AUTHORITY = "com.imooc.menuprovider1";
    private static final String TABLE_A = "type_tb";//菜品类型表
    private static final String TABLE_B = "dish_tb";//菜品表
    public static final Uri CONTENT_URI_A = Uri.parse("content://" + AUTHORITY + "/" + TABLE_A);
    public static final Uri CONTENT_URI_B = Uri.parse("content://" + AUTHORITY + "/" + TABLE_B);

    public loadDataDao() {
    }

    /// / 从CotentProvider获取数据 返回List<TypeList> 数据集
    public List<TypeList> loadDatas(ContentResolver resolver) {
        //将数据放进这里返回
        List<TypeList> mDatas = new ArrayList<>();
        //找到从CotentProvider的位置一样
        //1.第一遍查询菜品类型表
        //返回数据集
        Cursor c = resolver.query(CONTENT_URI_A,
                null,//指定查询列,null为不指定
                null,//查询条件,null为查询所有
                null,//查询条件值数组,搭配上面的参数使用
                null);//排序,null
        TypeList parent = null;
        while (c.moveToNext()) {
            //1.拿父item
            parent = new TypeList();
            int id = c.getInt(c.getColumnIndex(TypeList.TYPE_ID));
            String Typename = c.getString(c.getColumnIndex(TypeList.TYPE_NAME));
            parent.setId(id);
            parent.setTypename(Typename);
            mDatas.add(parent);
        }
        c.close();
        //  Log.e("TAG","拿到"+TABLE_A+"的数据"+parent.toString());
        DishList child = null;
        for (TypeList typeList : mDatas) {
            String pTypeName = typeList.getTypename();
            Log.e("TAG", "菜品类型:" + pTypeName);
            //2.根据父typename拿对应子item的数据
            Cursor c2 = resolver.query(CONTENT_URI_B,
                    null,
                    DishList.DISH_TYPE,//指定查询条件为父item的typename的列名
                    new String[]{pTypeName},//指定条件值数组为typename
                    null);
            while (c2.moveToNext()) {
                child = new DishList();
                int cid = c2.getInt(c2.getColumnIndex(DishList.DISH_ID));
                String cname = c2.getString(c2.getColumnIndex(DishList.DISH_NAME));
                child.setId(cid);
                child.setName(cname);
                child.setType(pTypeName);
                typeList.addChild(child);
                // Log.e("TAG","拿到"+TABLE_B+"的数据"+child.toString());
            }
            Log.e("TAG", "拿到的数据" + typeList.toString());
            c2.close();
        }
        return mDatas;
    }
}

在ContentProvider的数据提供的查询方法中

这里需要注意用UriMatcher类来解析Uri,可以访问不同的表

判断是要进行区分哪个表的操作

    private static final UriMatcher URI_MATCHER;
    private static final String AUTHORITY = "com.imooc.menuprovider1";
    private static final String TABLE_A = "type_tb";//菜品类型表
    private static final String TABLE_B = "dish_tb";//菜品表
    private static final int TABLE_A_MSG = 1000;//判断码
    private static final int TABLE_B_MSG = 1001;//判断码


    static {
        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
        URI_MATCHER.addURI(AUTHORITY, TABLE_A, TABLE_A_MSG);
        URI_MATCHER.addURI(AUTHORITY, TABLE_B, TABLE_B_MSG);
    }
....
 @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        Log.e("TAG", "执行了CotentProvider的查询操作");
        String table = null;
        String selection1 = null;
        String[] selectionArgs1 = null;
        String sortOrder1 = null;
        //通过解析传来的uri判断是要查询哪个表
        switch (URI_MATCHER.match(uri)) {
            case TABLE_A_MSG:
                table = TABLE_A;
                break;
            case TABLE_B_MSG:
                table = TABLE_B;
                selection1 = selection+"=?";//特别注意这里 要加"=?"
                selectionArgs1 = selectionArgs;
                break;
            default:
                break;
        }
        Cursor c = db.query(table,
                null,
                selection1,
                selectionArgs1,
                null,
                null,
                null);
        return c;

    }

步骤3:显示到expandableListView上

MainActivity.java绑定数据到适配器上,

public class MainActivity extends AppCompatActivity {

    private ExpandableListView ex_lv;
    private MenuAdapter mAdapter;
    private List<TypeList> mDatas;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //获取expandableListView控件
        ex_lv = findViewById(R.id.id_expandable_lv);
        //1.拿到ContentProvider数据
        ContentResolver contentResolver=getContentResolver();
        mDatas=new loadDataDao().loadDatas(contentResolver);
        //2.获取适配器对象
        mAdapter = new MenuAdapter(this,mDatas);
        //3.将适配器绑定到exlistview上显示
        ex_lv.setAdapter(mAdapter);

    }
}

因为适配器里面的视图和数据没做绑定,所以下面要实现适配器和数据的绑定

MenuAdapter.java
/**
 * 菜单适配器类
 * 主要功能:实现适配器和数据的绑定、视图的显示
 */
public class MenuAdapter extends BaseExpandableListAdapter {
    private static final String AUTHORITY = "com.imooc.menuprovider1";
    private static final String TABLE_A = "type_tb";//菜品类型表
    private static final String TABLE_B = "dish_tb";//菜品表
    public static final Uri CONTENT_URI_A = Uri.parse("content://" + AUTHORITY + "/" + TABLE_A);
    public static final Uri CONTENT_URI_B = Uri.parse("content://" + AUTHORITY + "/" + TABLE_B);
    private Context context;
    private List<TypeList> mDatas;//显示的数据集
    private LayoutInflater mInflater;

    //通过ContentResolver对获取数据
    private ContentResolver resolver;

    public MenuAdapter(Context context, List<TypeList> mDatas) {
        this.mDatas = mDatas;
        this.context = context;
        this.mInflater = LayoutInflater.from(context);
        resolver = context.getContentResolver();//拿到resolver对象
    }

    //获取父item的数目
    @Override
    public int getGroupCount() {
        return mDatas.size();
    }

    //获取每个父item下子item的数目
    @Override
    public int getChildrenCount(int groupPosition) {
        return mDatas.get(groupPosition).getChildren().size();
    }

    //获取父item的位置
    @Override
    public Object getGroup(int groupPosition) {
        return mDatas.get(groupPosition);
    }

    //每一个父item下子item的位置
    @Override
    public Object getChild(int groupPosition, int childPosition) {
        return mDatas.get(groupPosition).getChildren().get(childPosition);
    }

    //获取父item的id
    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    //获取子item的id
    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

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

    //父item的布局
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        ParentViewHolder pvh;
        if (convertView == null) {
            pvh = new ParentViewHolder();
            convertView = mInflater.inflate(
                    R.layout.activity_parent_item,//子item布局
                    parent,//子item里面的根布局
                    false);
            pvh.p_tv = convertView.findViewById(R.id.id_parent_item_tv);
            pvh.p_iv = convertView.findViewById(R.id.id_parent_item_iv);
            convertView.setTag(pvh);
        } else {
            pvh = (ParentViewHolder) convertView.getTag();
        }
        pvh.p_tv.setText(mDatas.get(groupPosition).getTypename());
        pvh.p_iv.setSelected(isExpanded);//通过判断是否展开来设置iv的状态显示不同的图片
        pvh.p_iv.setImageResource(R.drawable.indicator_group);
        return convertView;
    }

    //子item的布局
    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        ChildViewHolder cvh;
        if (convertView == null) {
            cvh = new ChildViewHolder();
            convertView = mInflater.inflate(
                    R.layout.activity_child_item,//子item布局
                    parent,//子item里面的根布局
                    false);
            cvh.c_tv = convertView.findViewById(R.id.id_child_item_tv);
            convertView.setTag(cvh);
        } else {
            cvh = (ChildViewHolder) convertView.getTag();
        }
        cvh.c_tv.setText(mDatas.get(groupPosition).getChildren().get(childPosition).getName());
        return convertView;
    }

    //优化性能
    private static class ParentViewHolder {
        TextView p_tv;
        ImageView p_iv;
    }

    private static class ChildViewHolder {
        TextView c_tv;
    }

    //子item是否可点击
    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }
}

效果:

还没完!

步骤4:实现长按子item删除功能,并实时更新

思路:在Adapter中的 getChildView方法里里对视图进行长按监听,弹出对话框

点击确认听到事件后数据源先删除(本地和数据提供者都删除(ContentProvider中的数据))

并通知Adapter执行(利用一个Callback回调每执行一次删除操作,回调一次更新数据)

madapter.notifyDataSetChanged();操作更新数据

    //子item的布局
    @Override
    public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        ChildViewHolder cvh;
        if (convertView == null) {
            cvh = new ChildViewHolder();
            convertView = mInflater.inflate(
                    R.layout.activity_child_item,//子item布局
                    parent,//子item里面的根布局
                    false);
            cvh.c_tv = convertView.findViewById(R.id.id_child_item_tv);
            convertView.setTag(cvh);
        } else {
            cvh = (ChildViewHolder) convertView.getTag();
        }
        cvh.c_tv.setText(mDatas.get(groupPosition).getChildren().get(childPosition).getName());
        //子item的长按事件
        convertView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Log.e("TAG", "子item的长按事件");
                Log.e("TAG", "长按的item的id是:" + mDatas.get(groupPosition).getChildren().get(childPosition).getId());
                AlertDialog.Builder builder = new AlertDialog.Builder(context);
                builder.setMessage("确定删除吗");
                builder.setTitle("提示");
                builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //点击取消,让对话框消失
                        dialog.dismiss();
                    }
                });
                //确定键的监听
                builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Log.e("TAG", "父item的位置" + groupPosition + "子item的位置" + childPosition);
                        //contentprovider数据删除,返回影响了行数
                        int result = resolver.delete(CONTENT_URI,
                                DishList.DISH_ID,
                                new String[]{(mDatas.get(groupPosition).getChildren().get(childPosition).getId()) + ""});
                        Log.e("TAG", "删除的id是:" + (mDatas.get(groupPosition).getChildren().get(childPosition).getId()));
                        //本地数据删除
                        mDatas.get(groupPosition).getChildren().remove(childPosition);
                        //回调新的数据到U线程,让adapter更新数据
                        callBack.DeleteSuccess(mDatas);
                        Log.e("TAG", "删除的后数据" + mDatas);
                        Log.e("TAG", "contentprovider中删除的数据影响的行数" + result);
                    }
                });
                builder.create().show();//一定记得写否则不显示
                // 这里一定要改为true,代表长按自己消费掉了,若为false,触发长按事件的同时,还会触发点击事件
                return true;
            }
        });
        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG", "子item的点击事件");
                Log.e("TAG", "子item的id是:" + mDatas.get(groupPosition).getChildren().get(childPosition).getId());
            }
        });

        return convertView;
    }
    //子item是否可点击
    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }

    //回调删除后的数据
    public interface CallBack {
        void DeleteSuccess(List<TypeList> mDatas);
    }

MainActivity,java中

       //2.获取适配器对象
        mAdapter = new MenuAdapter(this, mDatas, new MenuAdapter.CallBack() {
            @Override
            public void DeleteSuccess(List<TypeList> mDatas) {
                mAdapter.notifyDataSetChanged();
            }
        });

效果:


步骤5:实现添加数据,并显示到ExpandableListview上

思路:

新建一个AddDishAcitivity类来实现添加数据和跳转

添加数据(将数据通过ContentResolver对象插入到ContentProvider中变成NewDatas

返回会重新执行MainActivity的oncreate的方法,数据会重新加载到NewDatas的数据

首先学习一下Spinder下拉框的使用

https://blog.youkuaiyun.com/qq_17846019/article/details/83343909

AddDishAcitivity.java


/**
 * 这个类用实现菜单数据的添加
 */
public class AddDishActivity extends AppCompatActivity {
    private static final String AUTHORITY = "com.imooc.menuprovider1";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);

    private Spinner spinner;
    private EditText add_et;
    private Button add_btn;
    private ContentResolver resolver;
    private String dish_type;
    private String dish_name;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_data);
        //默认键盘不弹出
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
        initView();
        onListener();
    }


    //初始化布局
    private void initView() {
        spinner = findViewById(R.id.add_data_sp);
        add_et = findViewById(R.id.add_data_et);
        add_btn = findViewById(R.id.add_data_btn);
        resolver = getContentResolver();
    }

    //监听方法
    private void onListener() {
        //spinner添加点击事件
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                String[] arr = getResources().getStringArray(R.array.type_name_string_arr);
                Log.e("TAG", "选择了" + arr[position] + "类别");
                //拿到菜的类型
                dish_type = arr[position];
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });
        add_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //TODO insert data to contentProvider
                //拿到菜名
                dish_name = add_et.getText().toString();
                Log.e("TAG", "插入菜名为:" + dish_name);
                ContentValues values = new ContentValues();
                values.put(DishList.DISH_NAME, dish_name);
                values.put(DishList.DISH_TYPE, dish_type);
                Uri uri = resolver.insert(CONTENT_URI, values);
                //解析插入在uri后的id
                long insert_id = ContentUris.parseId(uri);
                Log.e("TAG", "插入成功id为:" + insert_id);
                startActivity(new Intent(AddDishActivity.this, MainActivity.class));
                finish();
            }
        });

    }
}

ContentProvidre对象的插入操作

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        long id = db.insert("dish_tb", null, values);
        return ContentUris.withAppendedId(uri, id);
    }

到这里就完成了。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值