任务描述
1. 安装已提供的含ContentProvider的菜单app(menudemo.apk)
2. 在自己的程序中访问ContentProvider(authorities是com.imooc.menuprovider,获取菜单数据,模拟手风琴效果进行布局(如图)
3.长按子项,弹出提示框 询问是否确定删除,选择【是】则删除,菜品能及时更新,否则不操作点击新增,则进入新增页面,在次页面选择类型,输入菜名,点击保存按钮,则进行菜品保存,回到主页,菜品能即使更新。
【备注】在往不同分组中添加菜品信息如,如果使用的是Spinner控件实现下拉框效果
步骤:
- 首先新建布局文件(ExpandableListView)的父item和子item和主布局
- 利用ContentResolver对象获取ContentProvider的数据Data
- 将数据Data绑定到ExpandableListView上显示
- 长按子item可以删除,并实时通知适配器更新数据
- 可以通过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);
}
到这里就完成了。