ContentProvider

  • ContentProvider和contentResolver是什么?
  • ContentResolver使用
    • URI是什么?
    • URI解析
  • ContentProvider使用
  • 案例1——添加日历事件
  • 案例2——获取通讯录联系人
  • 案例3——获取短信验证码
  • 案例4——访问媒体库
    • ImageItem.java
    • SizeUtils.java
    • activity_main.xml
    • MainActivity.java
    • item_image.xml
    • ResultImageAdapter.java
    • PickerConfig
    • activity_picker.xml
    • PickerActivity.java
    • ImageSectorAdapter.java
    • 运行效果图

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:

  1. 可用通配符*表示任意字符,#表示任意数字
  2. content://com.example.demo0.provider/* 意为匹配任意表
  3. 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

Tips:

  1. ContentProvider是对 DatabaseHelper的再封装,其增删改查方法都需要配合DatabaseHelper使用
  2. 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;}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL(CREATE_BOOK);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}

选择包new→Ohter→ContentProvider,输入name=MyContentProvider和authorities=com.example.database.provider,重写相关方法:

  • addURI()第一个参数为URI,第二个参数为表名,第三个参数为匹配结果
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);}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic boolean onCreate() {databaseHelper = new MyDatabaseHelper(getContext(), "Book.db", null, 1);return true;}@Overridepublic 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;}@Overridepublic 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"><Buttonandroid:id="@+id/add"android:layout_height="wrap_content"android:text="add"android:layout_width="match_parent"/><Buttonandroid:id="@+id/query"android:layout_height="wrap_content"android:text="query"android:layout_width="match_parent"/><Buttonandroid:id="@+id/update"android:layout_height="wrap_content"android:text="update"android:layout_width="match_parent"/><Buttonandroid: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;@Overrideprotected 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()) {@Overridepublic void onChange(boolean selfChange) {super.onChange(selfChange);}});Button add = findViewById(R.id.add);add.setOnClickListener(new View.OnClickListener() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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"><Buttonandroid: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";@Overrideprotected 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() {@Overridepublic 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";@Overrideprotected 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;}@Overridepublic 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/");@Overrideprotected 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()) {@Overridepublic 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"><Buttonandroid:id="@+id/get_pic"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="获取图片" /><androidx.recyclerview.widget.RecyclerViewandroid: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;@Overrideprotected 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() {@Overridepublic 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);}@Overridepublic 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"><ImageViewandroid:id="@+id/image_iv"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="centerCrop" /><Viewandroid:id="@+id/image_cover"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#77000000"android:visibility="gone" /><CheckBoxandroid: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@Overridepublic 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);}@Overridepublic 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);}@Overridepublic 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"><RelativeLayoutandroid:id="@+id/title"android:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:id="@+id/back"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="10dp"android:text="返回" /><TextViewandroid: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" /><Buttonandroid: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.RecyclerViewandroid: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;@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic 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@Overridepublic 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;}@Overridepublic 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);}}@Overridepublic 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();}@Overridepublic 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@Overridepublic 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);}@Overridepublic 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() {@Overridepublic 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;}@Overridepublic 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);}}
}

运行效果图

点击获取照片进入选择界面,选择完成后根据照片数量显示

下面是选择界面

Android基础——ContentProvider和contentResolver相关推荐

  1. android中contentProvider及ContentResolver

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程 这个技术是解决应用之间的一个调用,如常见的应用间数据库查询,内容提供者暴露接口,内容解析器Conten ...

  2. Android基础知识巩固系列 Android之四大组件——ContentProvider(内容提供者)

    因为最近要面试,于是打算整理整理一下Android的基础知识,由于之前本人已经学习过大概的Android基础知识,这里主要讲这四大组件.五大存储.六大布局.网络请求等这些内容,其他一些等有时间再整理, ...

  3. Android ContentProvider、ContentResolver和ContentObserver的使用

    1.ContentProvider.ContentResolver和ContentObserver ContentProvider是Android的四大组件之中的一个,可见它在Android中的作用非 ...

  4. 268.Android基础之ContentProvider(AS)

    ContentProvider是Android的四大组件之一,主要用于跨进程通信 ContentProvider的作用 使用现有的内容提供者来读取和操作相应程序中的数据 创建自己的内容提供者给自己程序 ...

  5. Android ContentProvider和ContentResolver实例

    ContentResolver和ContentProvider通过相同的URI联系,实现数据共享.我重写了public class MyAdapter extends BaseAdapter,putD ...

  6. Android基础四大组件详解

    Android四大组件详解 博主接触Android开发将近一年,从最初的JavaSE开始,到Android基础,一直学的糊糊涂涂,最近想整理一番 android基础, 顺便把自己的学习开发经验分享给大 ...

  7. Android 面试系列(一)Android 基础

    文章目录 序言 四大组件 Activity Activity 生命周期 onStart() 与 onResume() 区别? Activity 启动模式 launchMode 使用 Intent 标记 ...

  8. Android基础知识点学习总结

    Android基础知识点学习总结 安卓基础知识个人学习笔记分享~ 一.Android系统架构 Linux内核层→系统运行层→应用框架层→应用层 1.Linux内核层:Android系统是基于Linux ...

  9. Android学习笔记:Android基础知识点(不断更新中)

    1.Android学习笔记:OkHttp 2.Android学习笔记:更新UI的方法(UI线程和非UI线程) 3.Android学习笔记:Volley 4.Android学习笔记:Handler 5. ...

最新文章

  1. go语言中使用递归函数实现文件目录的遍历
  2. 程序的加载和执行(一)——《x86汇编语言:从实模式到保护模式》读书笔记21
  3. 快速设置 App 图标 - iOS/Android
  4. Matplotlib基础(part1)--基本绘图
  5. 17行html代码实现的将网页文本保存成本地文本文件
  6. vue封装axios接口
  7. jq输出文本_如何用 Linux 命令行工具解析和格式化输出 JSON | Linux 中国
  8. 0间隔24h采集线报+源码的资源网
  9. ide中tomcat乱码_idea tomcat 乱码问题的解决及相关设置
  10. .NetCore源码阅读笔记系列之Security (四) Authentication AddJwtBearer
  11. java方法语句错误需要标识符_java错误需要标识符_Java错误 找不到符号
  12. BAPI_FIXEDASSET_OVRTAKE_CREATE 创建资产并折旧
  13. C#查看打印机状态(缺纸)
  14. 应聘高校教师的试讲技巧
  15. 三年之期已至,加多宝如何续写上市新故事
  16. php实现pdhf2加密,搞了一天半了,可恶的加密代码解决方案
  17. android 关于刷app下载量的问题
  18. 服务器什么系统好用点,服务器用什么系统好
  19. 【.NET】EF框架之三种模式
  20. 877E - Danil and a Part-time Job

热门文章

  1. 遗传算法在离散型工厂选址问题中的应用
  2. 运动装备哪些品牌好?运动装备好物推荐
  3. 用Direct3D设计二维横版过关动作类游戏的角色运动(双倍速、二段跳)
  4. Allegro指定gerber生成路径
  5. Visual Studio 2015编译运行C语言文件问题小结
  6. 制造业生产运营管理系统如何定义?这些你做到了吗
  7. 程序员的梗_程序员的这些梗!你都知道吗?
  8. 人工智能创造艺术作品:创意对抗网络(CAN)
  9. 华为 荣耀MagicBook 2020 电脑 Hackintosh 黑苹果efi引导文件
  10. 学习JAVA游戏服务器开发需要了解的情况