ContentProvider
ContentProvider和contentResolver是什么?
利用ContentProvider和contentResolver可实现在不同应用程序之间的数据共享,并保证被访问数据的安全性。ContentProvider用于暴露数据,contentResolver用于操作数据。
ContentResolver使用
通过Context的getContentResolver()方法获取实例,通过Uri对指定应用的表进行增删改查
URI是什么?
Uri由content://authority/path/(id)组成,其中authority用于区分不用程序,path用于区分程序中不同的表,id用于指定表中的数据
列如:content://com.example.demo0.provider/table1/1 意为访问com.example.demo0应用table1中id为1的数据
Tips:
- 可用通配符*表示任意字符,#表示任意数字
- content://com.example.demo0.provider/* 意为匹配任意表
- content://com.example.demo0.provider/table1/# 意为匹配table1中的任意行
URI解析
通过Uri.parse()方法可将字符串解析为Uri对象:
Uri uri=Uri.parse("content://com.example.demo0.provider/table1");
增
第一个参数为uri,第二个参数为要插入的ContentValues,返回新纪录的Uri:
ContentValues values=new ContentValues();
values.put("column1","text1");
values.put("column2","text2");
getContentResolver().insert(uri,values);
删
第一个参数为uri,第二三个参数为约束条件,返回被删除的行数:
getContentResolver().delete(uri,"column2=?",new String[]{"text2"});
改
第一个参数为uri,第二个参数为要修改的ContentValues,第三四个参数为约束条件,返回影响行数:
ContentValues values=new ContentValues();
values.put("column1","");
getContentResolver().update(uri,values,"colum1=? and colum2=?",new String[]{"text",1});
查
第一个参数为uri,第二个参数为列名,第三四个参数为约束条件,第五个参数为对结果的排序方式,返回cursor:
Cursor cursor=getContentResolver().query(uri,null,null,null,null);
if(cursor!=null){
while(cursor.moveToNext()){
String column1=cursor.getString(cursor.getColumnIndex("column1"));
String column2=cursor.getString(cursor.getColumnIndex("column2"));
}
cursor.close();
}
ContentProvider使用
除了系统自带应用的ContentProvider,还可以通过继承ContentProvider,内部维护UriMatcher匹配Uri,重写以下方法实现自定义ContentProvider:
- onCreate():创建和升级数据库,返回true表示ContentProvider初始化成功
- query():查询数据,返回cursor对象
- insert():添加数据,返回新纪录的Uri
- update():更新数据,返回受影响行数
- delete():删除数据,返回被删除行数
- getType():返回Uri相应的MIME类型,若Uri以path结尾返回:vnd.anroid.cursor.dir/vnd.<authority>.<path>,以id结尾则将dir改为item
- onCreate()运行在主线程,其他方法运行在Binder线程池中
Tips:
- ContentProvider是对 DatabaseHelper的再封装,其增删改查方法都需要配合DatabaseHelper使用
- ContentProvider通过UriMatcher控制数据库的访问,从而保证数据安全
新建MyDatabaseHelper:
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book("
+ "id integer primary key autoincrement,"
+ "author text,"
+ "price real,"
+ "pages integer,"
+ "name text)";
private Context mContext;
public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
选择包new→Ohter→ContentProvider,输入name=MyContentProvider和authorities=com.example.database.provider,重写相关方法:
- addURI()第一个参数为URI,第二个参数为表名,第三个参数为匹配结果
- 修改数据后调用notifyChange(),才能回调ContentObserver的onChange()
public class MyContentProvider extends ContentProvider {
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final String AUTHORITY = "com.example.database.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper databaseHelper;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase writableDatabase = databaseHelper.getWritableDatabase();
int deleteRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
deleteRows = writableDatabase.delete("Book",selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deleteRows = writableDatabase.delete("Book", "id=?", new String[]{bookId});
break;
default:
break;
}
return deleteRows;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.database.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.database.provider.book";
}
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase writableDatabase = databaseHelper.getWritableDatabase();
Uri UriReturn = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = writableDatabase.insert("Book", null, values);
UriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
break;
}
getContext().getContentResolver().notifyChange(UriReturn,null);
return UriReturn;
}
@Override
public boolean onCreate() {
databaseHelper = new MyDatabaseHelper(getContext(), "Book.db", null, 1);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteDatabase writableDatabase = databaseHelper.getWritableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
cursor = writableDatabase.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
cursor = writableDatabase.query("Book", projection, "id=?", new String[]{bookId}, null, null, sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase writableDatabase = databaseHelper.getWritableDatabase();
int updateRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
updateRows = writableDatabase.update("Book",values,selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
updateRows = writableDatabase.update("Book", values, "id=?", new String[]{bookId});
break;
default:
break;
}
return updateRows;
}
}
修改activity_main.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:orientation="vertical"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/add"
android:layout_height="wrap_content"
android:text="add"
android:layout_width="match_parent"/>
<Button
android:id="@+id/query"
android:layout_height="wrap_content"
android:text="query"
android:layout_width="match_parent"/>
<Button
android:id="@+id/update"
android:layout_height="wrap_content"
android:text="update"
android:layout_width="match_parent"/>
<Button
android:id="@+id/delete"
android:layout_height="wrap_content"
android:text="delete"
android:layout_width="match_parent"/>
</LinearLayout>
修改MainActivity:
public class MainActivity extends AppCompatActivity {
private String newId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getContentResolver().registerContentObserver(Uri.parse("content://com.example.database.provider/book"), true, new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
});
Button add = findViewById(R.id.add);
add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Uri uri = Uri.parse("content://com.example.database.provider/book");
ContentValues values = new ContentValues();
values.put("name", "Tom");
values.put("author", "john");
values.put("pages", 100);
values.put("price", 10);
Uri newUri = getContentResolver().insert(uri, values);
newId = newUri.getPathSegments().get(1);
}
});
Button query = findViewById(R.id.query);
query.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Uri uri = Uri.parse("content://com.example.database.provider/book");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
String pages = cursor.getString(cursor.getColumnIndex("pages"));
String price = cursor.getString(cursor.getColumnIndex("price"));
Log.d("MainActivity", name + author + pages + price);
}
cursor.close();
}
}
});
Button update = findViewById(R.id.update);
update.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Uri uri = Uri.parse("content://com.example.database.provider/book/" + newId);
ContentValues values = new ContentValues();
values.put("price", 20);
getContentResolver().update(uri, values, null, null);
}
});
Button delete = findViewById(R.id.delete);
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Uri uri = Uri.parse("content://com.example.database.provider/book/" + newId);
getContentResolver().delete(uri, null, null);
}
});
}
}
案例1——添加日历事件
activitiy_main.xml布局文件只有一个按钮
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context=".MainActivity">
<Button
android:id="@+id/insertEvent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="添加日历事件" />
</RelativeLayout>
AndroidManifest.xml添加权限
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
MainActivity动态申请权限
- 通过Events的uri为日历添加事件,需要设置DTSTART、DTEND、EVENT_TIMEZONE等属性
- 通过Reminders的uri添加提醒事件,需要设置EVENT_ID、MINUTES、METHOD等属性
public class MainActivity extends AppCompatActivity {
private static final String TAG = "song";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkCalendarPermission();
//query();
Button insertBtn = findViewById(R.id.insertEvent);
insertBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Calendar beginTime = Calendar.getInstance();
// 分别为年-月(从0开始)-日-时-分
beginTime.set(2022, 0, 30, 0, 0);
long beginMillis = beginTime.getTimeInMillis();
Calendar endTime = Calendar.getInstance();
endTime.set(2022, 0, 30, 23, 59);
long endMillis = endTime.getTimeInMillis();
String timeZoneID = TimeZone.getDefault().getID();
//插入需要设置开始/结束时间、时区、日历id,其他的如标题描述自行添加
Uri eventUri = CalendarContract.Events.CONTENT_URI;
ContentResolver contentResolver = getContentResolver();
ContentValues eventValues = new ContentValues();
eventValues.put(CalendarContract.Events.DTSTART, beginMillis);
eventValues.put(CalendarContract.Events.DTEND, endMillis);
eventValues.put(CalendarContract.Events.CALENDAR_ID, 1);
eventValues.put(CalendarContract.Events.EVENT_TIMEZONE, timeZoneID);
eventValues.put(CalendarContract.Events.TITLE, "日历提醒");
eventValues.put(CalendarContract.Events.DESCRIPTION, "这里是日历事件描述");
eventValues.put(CalendarContract.Events.EVENT_LOCATION, "深圳");
Uri resultUri = contentResolver.insert(eventUri, eventValues);
Log.d(TAG, "resultUri=" + resultUri);
//插入事件和提醒是不同的表
String eventID = resultUri.getLastPathSegment();
Log.d(TAG, "eventID=" + eventID);
ContentValues reminderValues = new ContentValues();
reminderValues.put(CalendarContract.Reminders.EVENT_ID, Long.parseLong(eventID));
reminderValues.put(CalendarContract.Reminders.MINUTES, 15);
reminderValues.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALARM);
Uri reminderUri = CalendarContract.Reminders.CONTENT_URI;
contentResolver.insert(reminderUri, reminderValues);
}
});
}
private void checkCalendarPermission() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
int readPermission = checkSelfPermission(Manifest.permission.READ_CALENDAR);
int writePermission = checkSelfPermission(Manifest.permission.WRITE_CALENDAR);
if (readPermission == PackageManager.PERMISSION_GRANTED && writePermission == PackageManager.PERMISSION_GRANTED) {
} else {
requestPermissions(new String[]{Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR}, 1);
}
}
}
private void query() {
ContentResolver contentResolver = getContentResolver();
//Uri uri = Uri.parse("content://" + "com.android.calendar/" + "calendars");
Uri uri = CalendarContract.Calendars.CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
String[] columnNames = cursor.getColumnNames();
while (cursor.moveToNext()) {
for (String columnName : columnNames) {
Log.d(TAG, columnName + " == " + cursor.getString(cursor.getColumnIndex(columnName)));
}
}
cursor.close();
}
}
案例2——获取通讯录联系人
MainActivity如下,需要动态申请权限READ_CONTACTS,data1为列名
public class MainActivity extends AppCompatActivity {
private static final String TAG = "song";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkCalendarPermission();
getUserInfo();
}
private void getUserInfo() {
ContentResolver contentResolver = getContentResolver();
Uri contactsUri = Uri.parse("content://com.android.contacts/raw_contacts");
Cursor contactsCursor = contentResolver.query(contactsUri, new String[]{"contact_id", "display_name"}, null, null, null);
String[] columnNames = contactsCursor.getColumnNames();
List<UseInfo> useInfos = new ArrayList<>();
while (contactsCursor.moveToNext()) {
UseInfo useInfo = new UseInfo();
useInfo.setId(contactsCursor.getString(contactsCursor.getColumnIndex("contact_id")));
useInfo.setDisplayName(contactsCursor.getString(contactsCursor.getColumnIndex("display_name")));
/*for (String columnName : columnNames) {
Log.d(TAG, columnName + " = " + contactsCursor.getString(contactsCursor.getColumnIndex(columnName)));
}*/
useInfos.add(useInfo);
}
contactsCursor.close();
Uri phoneUri = Uri.parse("content://com.android.contacts/data/phones");
for (UseInfo useInfo : useInfos) {
Cursor phoneCursor = contentResolver.query(phoneUri, new String[]{"data1"}, "raw_contact_id=?", new String[]{useInfo.getId()}, null);
if (phoneCursor.moveToNext()) {
useInfo.setPhoneNum(phoneCursor.getString(0));
}
phoneCursor.close();
Log.d(TAG, "getUserInfo: userInfo = " + useInfo);
}
}
private void checkCalendarPermission() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
int readPermission = checkSelfPermission(Manifest.permission.READ_CONTACTS);
int writePermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
if (readPermission == PackageManager.PERMISSION_GRANTED && writePermission == PackageManager.PERMISSION_GRANTED) {
} else {
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS}, 1);
}
}
}
}
用到的javabean如下
public class UseInfo {
private String id;
private String displayName;
private String phoneNum;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(String phoneNum) {
this.phoneNum = phoneNum;
}
@Override
public String toString() {
return "UseInfo{" +
"id='" + id + '\'' +
", displayName='" + displayName + '\'' +
", phoneNum='" + phoneNum + '\'' +
'}';
}
}
案例3——获取短信验证码
需要申请READ_SMS权限,通过监听事件获取短信内容,正则表达式提取验证码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "song";
private Uri mSmsUri = Uri.parse("content://sms/");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermission();
//getSms();
getVerifyCode();
}
private static UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI("sms", "#", 1);
}
private void getVerifyCode() {
getContentResolver().registerContentObserver(mSmsUri, true, new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange, @Nullable Uri uri) {
Log.d(TAG, "onChange: ");
if (sUriMatcher.match(uri) == 1) {
Log.d(TAG, "onChange: uri=" + uri);
Cursor query = getContentResolver().query(mSmsUri, new String[]{"body"}, null, null, null);
if (query.moveToNext()) {
String body = query.getString(0);
Log.d(TAG, "onChange: body=" + body);
if (!TextUtils.isEmpty(body)) {
Pattern pattern = Pattern.compile("(?<![0-9])([0-9]{4})(?![0-9])");
Matcher matcher = pattern.matcher(body);
boolean b = matcher.find();
if (b) {
Log.d(TAG, "onChange: code=" + matcher.group());
}
}
}
}
}
});
}
private void getSms() {
ContentResolver contentResolver = getContentResolver();
Cursor smsCursor = contentResolver.query(mSmsUri, null, null, null, null);
String[] columnNames = smsCursor.getColumnNames();
while (smsCursor.moveToNext()) {
for (String columnName : columnNames) {
Log.d(TAG, columnName + " = " + smsCursor.getString(smsCursor.getColumnIndex(columnName)));
}
}
smsCursor.close();
}
private void checkPermission() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
int readPermission = checkSelfPermission(Manifest.permission.READ_SMS);
if (readPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.READ_SMS}, 1);
}
}
}
}
通过模拟器发送短信,即可在log中捕获
案例4——访问媒体库
ImageItem.java
javabean类,存储照片在sd卡的路径、名称、和添加日期
public class ImageItem {
private String path;
private String title;
private long date;
public ImageItem(String path, String title, long date) {
this.path = path;
this.title = title;
this.date = date;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public long getDate() {
return date;
}
public void setDate(long date) {
this.date = date;
}
}
SizeUtils.java
获取屏幕宽度,从而计算平分每张照片所占宽度
public class SizeUtils {
public static Point getScreenSize(Context context) {
Point point = new Point();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(point);
return point;
}
}
activity_main.xml
MainActivity适配布局,放置一个跳转Button和所选择照片返回显示的RecycleView
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context=".MainActivity">
<Button
android:id="@+id/get_pic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="获取图片" />
<androidx.recyclerview.widget.RecyclerView
android:layout_below="@id/get_pic"
android:id="@+id/result_image_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
MainActivity.java
- checkPermission()动态申请READ_EXTERNAL_STORAGE权限
- initEvent()为按钮设置点击事件,跳转到选择界面的Activity
- initPickerConfig()使用单例实现两个Activity的数据传递(最大选择数量和选择完后的回调)
- onSelectedFinished()为选择完后的回调,设置数据、适配器和布局
public class MainActivity extends AppCompatActivity implements PickerConfig.OnImageSelectedFinishedListener {
private static final int MAX_SELECTED_COUNT = 9;
private Button mGetPicBtn;
private RecyclerView mResultImageRv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermission();
initView();
initEvent();
initPickerConfig();
}
private void checkPermission() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
int readPermission = checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
if (readPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
}
}
private void initView() {
mGetPicBtn = findViewById(R.id.get_pic);
mResultImageRv = findViewById(R.id.result_image_list);
}
private void initEvent() {
mGetPicBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, PickerActivity.class));
}
});
}
private void initPickerConfig() {
PickerConfig pickerConfig = PickerConfig.getInstance();
pickerConfig.setMaxSelectedCount(MAX_SELECTED_COUNT);
pickerConfig.setOnImageSelectedFinishedListener(this);
}
@Override
public void onSelectedFinished(List<ImageItem> result) {
int col = Math.min(result.size(), 3);
ResultImageAdapter resultImageAdapter = new ResultImageAdapter();
resultImageAdapter.setData(result, col);
mResultImageRv.setAdapter(resultImageAdapter);
mResultImageRv.setLayoutManager(new GridLayoutManager(this, col));
}
}
item_image.xml
为适配布局,有一个显示图片ImageVIew、选中后的背景颜色覆盖View和CheckBox选择框
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/image_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_iv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<View
android:id="@+id/image_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#77000000"
android:visibility="gone" />
<CheckBox
android:id="@+id/image_chek_box"
android:clickable="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:checked="false" />
</RelativeLayout>
ResultImageAdapter.java
为返回结果的适配器
- 数据由MainActivity设置,onBindViewHolder()根据列数计算每张图片占比来显示图片
public class ResultImageAdapter extends RecyclerView.Adapter<ResultImageAdapter.innerHolder> {
private List<ImageItem> mImageItems = new ArrayList<>();
private int mCol = 1;
@NonNull
@Override
public innerHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_image, parent, false);
itemView.findViewById(R.id.image_chek_box).setVisibility(View.GONE);
return new innerHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ResultImageAdapter.innerHolder holder, int position) {
View itemView = holder.itemView;
Point screenSize = SizeUtils.getScreenSize(itemView.getContext());
RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(screenSize.x / mCol, screenSize.x / mCol);
itemView.setLayoutParams(layoutParams);
ImageView imageView = itemView.findViewById(R.id.image_iv);
ImageItem imageItem = mImageItems.get(position);
Glide.with(imageView.getContext()).load(imageItem.getPath()).into(imageView);
}
@Override
public int getItemCount() {
return mImageItems.size();
}
public void setData(List<ImageItem> result, int horizontalCount) {
this.mCol = horizontalCount;
mImageItems.clear();
mImageItems.addAll(result);
notifyDataSetChanged();
}
public class innerHolder extends RecyclerView.ViewHolder {
public innerHolder(@NonNull View itemView) {
super(itemView);
}
}
}
PickerConfig
为主页面和选择页面的单例桥梁,持有最大选择数量和数据回调
public class PickerConfig {
private int maxSelectedCount = 1;
private OnImageSelectedFinishedListener mImageSelectedFinishedListener = null;
private PickerConfig() {
}
private static PickerConfig sPickerConfig;
public static PickerConfig getInstance() {
if (sPickerConfig == null) {
sPickerConfig = new PickerConfig();
}
return sPickerConfig;
}
public int getMaxSelectedCount() {
return maxSelectedCount;
}
public void setMaxSelectedCount(int maxSelectedCount) {
this.maxSelectedCount = maxSelectedCount;
}
public OnImageSelectedFinishedListener getImageSelectedFinishedListener() {
return mImageSelectedFinishedListener;
}
public void setOnImageSelectedFinishedListener(OnImageSelectedFinishedListener listener) {
this.mImageSelectedFinishedListener = listener;
}
public interface OnImageSelectedFinishedListener {
void onSelectedFinished(List<ImageItem> result);
}
}
activity_picker.xml
选择界面布局,由上方的标题栏(返回,选择数量提示和完成)及下面的RecycleView组成
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context=".PickerActivity">
<RelativeLayout
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="返回" />
<TextView
android:id="@+id/selectedCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/finished"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:text="已选择(0/9)"
android:textSize="20sp" />
<Button
android:id="@+id/finished"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:text="完成"
android:textSize="20sp" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/image_selector"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/title" />
</RelativeLayout>
PickerActivity.java
- initLoaderManager()利用ContentProvider获取媒体库图片数据设置到适配器
- initEvent()设置适配器、返回/完成按钮的点击事件,点击完成后需要将选中的图片数据回调
- initConfig()为设配器设置最大选择数量
public class PickerActivity extends AppCompatActivity implements ImageSectorAdapter.OnItemSelectedChangeListener {
private static final String TAG = "song";
private static final int LOADER_ID = 1;
private List<ImageItem> mImageItems = new ArrayList<>();
private ImageSectorAdapter mImageSectorAdapter;
private TextView mSelectedCount;
private Button mFinishedBtn;
private PickerConfig mPickerConfig;
private RecyclerView mImageSectorRv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_picker);
//query();
initLoaderManager();
initView();
initEvent();
initConfig();
}
private void initConfig() {
mPickerConfig = PickerConfig.getInstance();
int maxSelectedCount = mPickerConfig.getMaxSelectedCount();
mImageSectorAdapter.setMaxSelectedCount(maxSelectedCount);
}
private void initView() {
mImageSectorRv = findViewById(R.id.image_selector);
mSelectedCount = findViewById(R.id.selectedCount);
mFinishedBtn = findViewById(R.id.finished);
}
private void initEvent() {
mImageSectorAdapter = new ImageSectorAdapter();
mImageSectorAdapter.setOnItemSelectedChangeListener(this);
mImageSectorRv.setAdapter(mImageSectorAdapter);
mImageSectorRv.setLayoutManager(new GridLayoutManager(this, 3));
mFinishedBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
List<ImageItem> result = new ArrayList<>(mImageSectorAdapter.getSelectedList());
if (result.size() != 0) {
mImageSectorAdapter.release();
PickerConfig.OnImageSelectedFinishedListener imageSelectedFinishedListener = mPickerConfig.getImageSelectedFinishedListener();
if (imageSelectedFinishedListener != null) {
imageSelectedFinishedListener.onSelectedFinished(result);
}
}
finish();
}
});
findViewById(R.id.back).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
private void initLoaderManager() {
mImageItems.clear();
LoaderManager loaderManager = LoaderManager.getInstance(this);
loaderManager.initLoader(LOADER_ID, null, new LoaderManager.LoaderCallbacks<Cursor>() {
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {
if (id == LOADER_ID) {
return new CursorLoader(PickerActivity.this,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{"_data", "_display_name", "date_added"},
null, null, "date_added DESC");
}
return null;
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
if (cursor != null) {
while (cursor.moveToNext()) {
String path = cursor.getString(0);
String title = cursor.getString(1);
long date = cursor.getLong(2);
ImageItem imageItem = new ImageItem(path, title, date);
mImageItems.add(imageItem);
}
cursor.close();
mImageSectorAdapter.setData(mImageItems);
}
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
}
});
}
private void query() {
ContentResolver contentResolver = getContentResolver();
Uri imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor query = contentResolver.query(imageUri, null, null, null, null);
String[] columnNames = query.getColumnNames();
while (query.moveToNext()) {
for (String columnName : columnNames) {
Log.d(TAG, columnName + " = " + query.getString(query.getColumnIndex(columnName)));
}
}
query.close();
}
@Override
public void OnItemSelectedChange(List<ImageItem> selectedList) {
mSelectedCount.setText("已选择(" + selectedList.size() + "/" + mImageSectorAdapter.getMaxSelectedCount() + ")");
}
}
ImageSectorAdapter.java
选择界面的适配器
- 一个ArrayList记录媒体库图片,一个ArrayList记录选中的图片
- onCreateViewHolder()设置每行3列
- onBindViewHolder()找到控件,选中时背景置灰,勾选ChekBox,回调选中的数量给PickerActivity修改UI
public class ImageSectorAdapter extends RecyclerView.Adapter<ImageSectorAdapter.innerHolder> {
private List<ImageItem> mImageItems = new ArrayList<>();
private List<ImageItem> mSelectedList = new ArrayList<>();
private OnItemSelectedChangeListener mOnItemSelectedChangeListener;
private int maxSelectedCount;
public int getMaxSelectedCount() {
return maxSelectedCount;
}
public void setMaxSelectedCount(int maxSelectedCount) {
this.maxSelectedCount = maxSelectedCount;
}
public List<ImageItem> getSelectedList() {
return mSelectedList;
}
public void setSelectedList(List<ImageItem> selectedList) {
mSelectedList = selectedList;
}
@NonNull
@Override
public innerHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_image, parent, false);
Point point = SizeUtils.getScreenSize(itemView.getContext());
RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(point.x / 3, point.x / 3);
itemView.setLayoutParams(layoutParams);
return new innerHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ImageSectorAdapter.innerHolder holder, int position) {
View itemView = holder.itemView;
View imageContainer = itemView.findViewById(R.id.image_container);
ImageView imageView = itemView.findViewById(R.id.image_iv);
CheckBox check = itemView.findViewById(R.id.image_chek_box);
View imageCover = itemView.findViewById(R.id.image_cover);
ImageItem imageItem = mImageItems.get(position);
Glide.with(imageView.getContext()).load(imageItem.getPath()).into(imageView);
if (mSelectedList.contains(imageItem)) {
check.setChecked(true);
imageCover.setVisibility(View.VISIBLE);
} else {
check.setChecked(false);
imageCover.setVisibility(View.GONE);
}
imageContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//已选中则取消选择
if (mSelectedList.contains(imageItem)) {
mSelectedList.remove(imageItem);
check.setChecked(false);
imageCover.setVisibility(View.GONE);
} else { //未选中则选择
if (mSelectedList.size() >= maxSelectedCount) {
Toast.makeText(check.getContext(), "最多只能选" + maxSelectedCount + "张图片", Toast.LENGTH_SHORT).show();
return;
}
mSelectedList.add(imageItem);
check.setChecked(true);
imageCover.setVisibility(View.VISIBLE);
}
if (mOnItemSelectedChangeListener != null) {
mOnItemSelectedChangeListener.OnItemSelectedChange(mSelectedList);
}
}
});
}
public void release() {
mSelectedList.clear();
}
public interface OnItemSelectedChangeListener {
void OnItemSelectedChange(List<ImageItem> selectedList);
}
public void setOnItemSelectedChangeListener(OnItemSelectedChangeListener listener) {
this.mOnItemSelectedChangeListener = listener;
}
@Override
public int getItemCount() {
return mImageItems.size();
}
public void setData(List<ImageItem> imageItems) {
mImageItems.clear();
mImageItems.addAll(imageItems);
notifyDataSetChanged();
}
public class innerHolder extends RecyclerView.ViewHolder {
public innerHolder(@NonNull View itemView) {
super(itemView);
}
}
}
运行效果图
点击获取照片进入选择界面,选择完成后根据照片数量显示
下面是选择界面