当前位置:   article > 正文

Android基础——ContentProvider和contentResolver

contentresolver

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");
  • 1

第一个参数为uri,第二个参数为要插入的ContentValues,返回新纪录的Uri:

ContentValues values=new ContentValues();
values.put("column1","text1");
values.put("column2","text2");
getContentResolver().insert(uri,values);
  • 1
  • 2
  • 3
  • 4

第一个参数为uri,第二三个参数为约束条件,返回被删除的行数:

getContentResolver().delete(uri,"column2=?",new String[]{"text2"});
  • 1

第一个参数为uri,第二个参数为要修改的ContentValues,第三四个参数为约束条件,返回影响行数:

ContentValues values=new ContentValues();
values.put("column1","");
getContentResolver().update(uri,values,"colum1=? and colum2=?",new String[]{"text",1});
  • 1
  • 2
  • 3

第一个参数为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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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:

  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;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

选择包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);
    }

    @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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100

修改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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

修改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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

案例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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

AndroidManifest.xml添加权限

<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
  • 1
  • 2

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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

案例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);
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

用到的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 + '\'' +
                '}';
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

案例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);
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

通过模拟器发送短信,即可在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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

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() + ")");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119

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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105

运行效果图

点击获取照片进入选择界面,选择完成后根据照片数量显示
在这里插入图片描述
下面是选择界面
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/932779
推荐阅读
相关标签
  

闽ICP备14008679号