Android学习笔记MVVM框架

一、文件创建目录结构

  • 工程项目创建完成后,默认打开activity.main.xml与MainActivity.java文件

  • 目录结构

1.1 app模块

1.2 Gradle Scripts工程编译配置文件

1.2.1 build.gradle 编译文件


android {
    compileSdkVersion 33    //sdk版本号

    defaultConfig {
        minSdkVersion 24     //最小支持sdk版本号
        targetSdkVersion 33  //最佳sdk版本号
        versionCode 1        //指定App应用版本号,必须是整数
        versionName "1.0"    //版本名称

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" //单元测试
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
//依赖项
}
//单个项目配置阿里云镜像
buildscript {
    repositories {
        maven {url 'http://maven.aliyun.com/nexus/content/groups/public/'}//这里
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.0'
    }
}
allprojects {
    repositories {
        google()
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }//这里
        jcenter()
    }
}

1.2.2 AndroidManifest.xml清单文件

android: allowBackup备份

icon图标

label手机屏上显示的名称,app名称

roundIcon圆角图标

theme主题

activity 活动页面注册声明

1.3编写一个页面流程

XML+JAVA

  • layout目录下创建xml文件,里面需要的文本写在strings.xml里

  • 创建与xml对应的Java代码,需要extends AppCompatActivity,因为它里面处理了很多兼容性问题,重写oncreate方法,执行布局。

  • 在清单中注册页面配置,这里注册的是Java代码

  • 可以一键生成,自动注册

二、基础知识

2.1 Activity

  • Activity是安卓开发四大组件之一

2.1.1 activity启动与结束


startActivity(new Intent(原页面.this,跳转目标页面.class));//跳转
finish();//当前activity结束

2.1.2 生命周期

2.2 数据库框架GreenDAO

  • 配置

  • 实体类贴标签

  • Util中 private DaoSession daoSession;

  • 创建数据库


DaoMaster.DevOpenHelper devOpenHelper=new DaoMaster.DevOpenHelper(this,"xxx.dp");
        SQLiteDatabase sqLiteDatabase = devOpenHelper.getWritableDatabase();
        DaoMaster daoMaster = new DaoMaster(sqLiteDatabase);
        daoSession = daoMaster.newSession();

2.3 蓝牙钥匙2000cKey

2.3.1 onActivityResult回调方法、onRequestPermissionsResult回调方法

在Activity页面finish时,会将resultCode和resultIntent的数据打包放在原来的Activity的ActivityRecord里面,原来的Activity页面进行onResume时,会将ActivityResult的数据提取出来进行处理,动态权限就回调onRequestPermissionsResult,其他情况回调onActivityResult。

2.3.2 权限获取与回调

  1. 检查权限时,在机器上弹出选择框,当你选择完是否获取权限后,执行onRequestPermissionResult回调函数,获取选择的结果。

  1. 机器弹出获取权限选择框,这时如果继续执行检查权限后的语句,不会等待权限是否获取,容易出现问题,所以一般是在执行onRequestPermissionResult之后确认获取权限的位置之后,再进行相关函数的执行。

  1. 权限获取示例,这里oncreate只要checkCameraPermission()就可以了,联动检查全部权限。


private static final int ST_PERMISSION_REQUEST_CAMERA = 1;
private static final int ST_PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 2;
private static final int ST_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 3;

private void checkCameraPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (checkSelfPermission(Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {//只有当!=PackageManager.PERMISSION_GRANTED时候才执行,保证只在第一次打开软件时候申请权限(4)
            requestPermissions(new String[]{Manifest.permission.CAMERA},
                    ST_PERMISSION_REQUEST_CAMERA);
        }
    }
}

private void checkReadPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                    ST_PERMISSION_REQUEST_READ_EXTERNAL_STORAGE);
        }
    }
}

private void checkWritePermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    ST_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE);
        }
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {//第一次打开软件可以进来
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == ST_PERMISSION_REQUEST_CAMERA) {
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Log.e("----","cameraPermisssion");
            checkReadPermission();//联动申请读

        }
    } else if (requestCode == ST_PERMISSION_REQUEST_READ_EXTERNAL_STORAGE) {
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Log.e("----","readPermisssion");
            checkWritePermission();//联动申请写
        }
    } else if (requestCode == ST_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) {

        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Log.e("----","writePermission");
           
        } else if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
            Log.e("----","write申请失败");
        }
    }
  1. 蓝牙钥匙主页面oncreate方法中调用checkPermissions()检查权限。


private void checkPermissions() {
        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        //判断蓝牙是否开启
        if (!bluetoothAdapter.isEnabled()) {
            Toast.makeText(this, "Open ble", Toast.LENGTH_LONG).show();
            return;
        }

        //清单中注册的权限集合
        String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE};
        List<String> permissionDeniedList = new ArrayList<>();
        for (String permission : permissions) {
            int permissionCheck = ContextCompat.checkSelfPermission(this, permission);
            //判断权限是否开启,有权限
            if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
                onPermissionGranted(permission);
            } else {
                permissionDeniedList.add(permission);//无权限
            }
        }
        //无权限列表不为空
        if (!permissionDeniedList.isEmpty()) {
            String[] deniedPermissions = permissionDeniedList.toArray(new String[permissionDeniedList.size()]);
            ActivityCompat.requestPermissions(this, deniedPermissions, REQUEST_CODE_PERMISSION_LOCATION);
        }
    }

有权限调用onPermissionGranted(String permission)


private void onPermissionGranted(String permission) {
        switch (permission) {
            //GPS定位权限
            case Manifest.permission.ACCESS_FINE_LOCATION:
                //当前安卓sdk版本大于等于6.0并且没开GPS
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !checkGPSIsOpen()) {
                    new AlertDialog.Builder(this)
                            .setTitle("提示")
                            .setMessage("当前手机扫描蓝牙需要打开定位功能")
                            .setNegativeButton("取消",
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            finish();
                                        }
                                    })
                            .setPositiveButton("前往设置",
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                                            startActivityForResult(intent, REQUEST_CODE_OPEN_GPS);
                                        }
                                    })

                            .setCancelable(false)
                            .show();
                } else {
                    BleKeySdk.getInstance().startScan(10*1000,bleScanCallback);
                }
                break;
        }
    }

//检查GPS是否开启
private boolean checkGPSIsOpen() {
        LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
        if (locationManager == null)
            return false;
        return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}

两个回调方法,嵌套的GPS走onActivityResult方法。


    @Override
    public final void onRequestPermissionsResult(int requestCode,
                                                 @NonNull String[] permissions,
                                                 @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case REQUEST_CODE_PERMISSION_LOCATION:
                if (grantResults.length > 0) {
                    for (int i = 0; i < grantResults.length; i++) {
                        if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                            onPermissionGranted(permissions[i]);
                        }
                    }
                }
                break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_OPEN_GPS) {
            if (checkGPSIsOpen()) {
                //setScanRule();
                //startScan();
                BleKeySdk.getInstance().startScan(60*10*1000,bleScanCallback);
            }
        }
    }

扫描蓝牙钥匙


BleScanCallback bleScanCallback = new BleScanCallback(){

        @Override
        public void onScanStarted(boolean success) {
            fab.setShowProgressBackground(true);
            fab.setIndeterminate(true);
        }
        @Override
        public void onScanning(BleDevice bleDevice) {
            if (!map.containsKey(bleDevice.getMac())) {
                map.put(bleDevice.getMac(), bleDevice);
                bleDeviceAdapter.addData(bleDevice);
            }
        }
        @Override
        public void onScanFinished(List<BleDevice> scanResultList) {
            fab.hideProgress();
        }
};

onCreate方法


@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_key_link);
        // 全屏
        getWindow()
                .setFlags(
                        WindowManager.LayoutParams.FLAG_FULLSCREEN,
                        WindowManager.LayoutParams.FLAG_FULLSCREEN);
        initView();
        BleKeySdk.getInstance().initSdk(this, null);
        checkPermissions();
    }
private FloatingActionButton fab;//这里是一个浮动动画按钮,需要在buliding里提娜佳依赖
private void initView() {
        mRecyclerView = findViewById(R.id.list);
        LinearLayoutManager lm = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        mRecyclerView.setLayoutManager(lm);
        bleDeviceAdapter = new BleDeviceAdapter(list);
        mRecyclerView.setAdapter(bleDeviceAdapter);
        bleDeviceAdapter.setOnItemClickListener(
                new OnItemClickListener() {
                    //BaseQuickAdapter旧版本与新版本实现方法不一致
                    @Override
                    public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
                        BleDevice bleDevice = (BleDevice) adapter.getData().get(position);
                        startActivity(
                                new Intent(SourceLockActivity.this, KeyActivity.class)
                                        .putExtra("mac", bleDevice.getMac()));
                    }
                });
        fab = findViewById(R.id.floatingActionButton);
        fab.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        map.clear();
                        bleDeviceAdapter.getData().clear();
                        BleKeySdk.getInstance().stopScan();
                        BleKeySdk.getInstance().startScan(10 * 1000, bleScanCallback);
                    }
                });
    }

2.4控件视图布局方式

2.4.1 文本显示


<TextView
            android:id="@+id/bleName"         //ID
            android:layout_width="0dp"        //文本框宽度
            android:layout_weight="150"       //倍数,可以用来设置等比例
            android:layout_height="wrap_content"  //文本框高度
            android:layout_gravity="center"      //指定当前视图相对于上级视图的对齐方式left|top左上
                                                 //gravity属性指定下级视图相对于上级视图的对齐方式

            android:fontFamily="sans-serif-condensed"
            android:paddingLeft="5dp"          //padding属性,指定当前视图与内部下级视图之间的距离
                                               //layout_margin属性,指定当前视图与周围评级视图之间距离
            android:text="蓝牙名称:"           //文本框内容
            android:textColor="@color/main_black_text_color"
            android:textSize="30sp"
            android:textStyle="bold"/>       //文本加粗

编辑框,按钮等控件与文本框使用方式相似。

  • 点击监听器:通过setOnClickListener方法设置。按钮被按住少于500毫秒时,会触发点击事件。

  • 长按监听器:通过setOnLongClickListener方法设置。按钮被按住超过500毫秒时,会触发长按事件。

遇见问题

1.在代码中直接使用setWidth ()方法,发现不起作用

textview.setwidth(100);//不起作用

解决方法:

使用TextView的getLayoutParams().width,然后赋值

textview.getLayoutParams() .width = 100;

2.4.2 常用布局

线性布局方式


<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">//(horizontal为水平,vertical为竖直)

相对布局RelativeLayout

  • 与该视图平级的其他视图

  • 上级视图(也就是它归属的RelativeLayout)

2.4.3 图像显示

图片一般放在res/drawable目录下,ImageView本身默认图片居中显示,若要改变图片的显示方式,可通过scaleType属性设定。

2.5 数据处理

2.5.1 activity页面间传递数据

向下一个页面传递数据

Intent重载了很多putExtra方法用于传递各种类型的信息,包括整数类型,字符串等。但是显然通过调用putExtra方法会很不好管理,因为数据都是零碎传递。所以Android引入了Bundle,其内部是一个Map,使用起来也和Map一样。


向下一个页面传递数据
Intent intent = new Intent(this, NextActivity.class);
//通过bundle包装数据
Bundle bundle = new Bundle();
bundle.putString("stringKey", "stringValue");
intent.putExtras(bundle);
startActivity(intent);
接收传递数据
Bundle bundle = getIntent().getExtras();
String stringValue = bundle.getString("stringKey");

返回数据给上一个页面

利用回调函数


上一个页面跳转到下一个页面,同时携带数据
private ActivityResultLauncher<Intent> register;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main2);

    findViewById(R.id.bt).setOnClickListener(this);

    //回调函数,返回到这个页面时所执行的程序
    register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
                   //回调函数
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result != null) {
                        Intent intent = result.getData();
                        if (intent != null && result.getResultCode() == Activity.RESULT_OK) {
                            //获取到返回的数据
                            Bundle bundle = intent.getExtras();
                        }
                    }
                }
            });
}

@Override
public void onClick(View v) {
    Intent intent = new Intent(this, MainActivity3.class);
    //跳转下一页面
    register.launch(intent);

}
下一个页面接受到数据,处理之后返回结果给上一个页面     
Bundle bundle = getIntent().getExtras();
//...页面进行处理
//返回数据给上一个页面
Bundle bundle = new Bundle();
bundle.putString("stringKey", "stringValue");
intent.putExtras(bundle);
setResult(Activity.RESULT_OK, intent);
finish();

2.5.2 获取资源和元数据信息


//获取strings.xml中的字符串资源
String text = getString(R.string.text);
//获取color.xml中的颜色资源
int black = getColor(R.color.black);

try {
    //获取包管理器
    PackageManager pm = getPackageManager();
    //获取当前的Activity信息
    ActivityInfo activityInfo = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
    Bundle bundle = activityInfo.metaData;
    String text2 = bundle.getString("text2");
} catch (PackageManager.NameNotFoundException e) {
    e.printStackTrace();
}

2.5.3共享参数SharedPreferences

sharedPreferences是安卓的一个轻量级存储工具,采用的方式是key-value,以xml文件形式存在,文件路径为/data/data/应用包名/shared_prefs/文件名.xml。

获取数据可以通过SharedPreferences对象获取:


//第一个参数表示文件名,第二个参数表示私有模式
SharedPreferences shared = getSharedPreferences("fileName", MODE_PRIVATE);
String name = shared.getString("name");

而存储数据则还需要借助Editor类:


//第一个参数表示文件名,第二个参数表示私有模式
SharedPreferences shared = getSharedPreferences("fileName", MODE_PRIVATE);
SharedPreferences.Editor editor = shared.edit();
editor.putString("name", "oymn");
editor.putInt("age", 20);
editor.commit();

2.6 存储卡

为了更规范地管理手机存储空间,Android从7.0开始将存储卡划分为私有存储和公共存储两大部分,也就是分区存储方式,系统给每个App都分配了默认的私有存储空间。App在私有空间上读写文件无须任何授权,但是若想在公共空间读写文件,则要在AndroidManifest.xml里面添加下述的权限配置。


<!-- 存储卡读写 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAG"/>

 //获取系统的公共存储路径
String publicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();

//获取系统的私有存储路径
String privatePath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();

boolean isLegacy = true;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
    //Android10的存储空间默认采用分区方式,这里是判断是使用传统方式还是分区方式
    isLegacy = Environment.isExternalStorageLegacy();
}

2.6.1 在存储卡上读写文件

文本文件的读写借助IO流 FileOutputStream(写文件)和 FileInputStream(读文件)


// 把字符串保存到指定路径的文本文件
public static void saveText(String path, String txt) {
    // 根据指定的文件路径构建文件输出流对象
    try (FileOutputStream fos = new FileOutputStream(path)) {
           fos.write(txt.getBytes()); // 把字符串写入文件输出流
    } catch (Exception e) {
        e.printStackTrace();
    }
}
// 从指定路径的文本文件中读取内容字符串
public static String openText(String path) {
    String readStr = "";
    // 根据指定的文件路径构建文件输入流对象
    try (FileInputStream fis = new FileInputStream(path)) {
        byte[] b = new byte[fis.available()];
        fis.read(b); // 从文件输入流读取字节数组
        readStr = new String(b); // 把字节数组转换为字符串
    } catch (Exception e) {
        e.printStackTrace();
    }
    return readStr; // 返回文本文件中的文本字符串
}

2.6.2 在存储卡上读写 图片文件

文本文件可以转化为对字符串的读写,而图像的读写就需要借助专门的位图工具Bitmap处理。不同图像来源获取Bitmap的方式不同,有三种:

  1. 从指定资源文件中获取:decodeResource,例如从资源文件img.png获取位图对象:


Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img);
  1. 从指定路径下获取:decodeFile,但是要注意从Android10开始,该方法只能获取私有空间下的图片,公共空间下获取不了。


Bitmap bitmap = BitmapFactory.decodeFile("C:\\Users\\OYMN\\Pictures\\onepunch.jpg");
  1. 从指定的输入流中获取,比如使用IO流打开图片文件,然后作为参数传入decodeStream:


public static Bitmap openImage(String path) {
    Bitmap bitmap = null; // 声明一个位图对象
    // 根据指定的文件路径构建文件输入流对象
    try (FileInputStream fis = new FileInputStream(path)) {
        bitmap = BitmapFactory.decodeStream(fis); // 从文件输入流中解码位图数据
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bitmap; // 返回图片文件中的位图数据
}

获取到图片之后就可以通过ImageView的setImageBitmap进行设置了。

有多种读取图片的方式,但是写图片只有一种方式。通过Bitmap的compress方法将位图数据压缩到文件输出流:


public static void saveImage(String path, Bitmap bitmap){
    //根据文件路径构建文件输出流
    try(FileOutputStream fos = new FileOutputStream()){
        //将位图数据压缩到文件输出流
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
    }catch(Exception e){
        e.printStackTrace();
    }
}

以下演示一下完整的文件读写操作:


// 获取当前App的私有下载目录
String path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() +
"/";
// 从指定的资源文件中获取位图对象
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.huawei);
String file_path = path + DateUtil.getNowDateTime("") + ".jpeg";
FileUtil.saveImage(file_path, bitmap); // 把位图对象保存为图片文件
tv_path.setText("图片文件的保存路径为:\n" + file_path);

// 获取当前App的私有下载目录
mPath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/";
// 获得指定目录下面的所有图片文件
mFilelist = FileUtil.getFileList(mPath, new String[]{".jpeg"});
if (mFilelist.size() > 0) {
// 打开并显示选中的图片文件内容
String file_path = mFilelist.get(0).getAbsolutePath();
tv_content.setText("找到最新的图片文件,路径为"+file_path);
// 显示存储卡图片文件的第一种方式:直接调用setImageURI方法
//iv_content.setImageURI(Uri.parse(file_path)); // 设置图像视图的路径对象
// 第二种方式:先调用BitmapFactory.decodeFile获得位图,再调用setImageBitmap方法
//Bitmap bitmap = BitmapFactory.decodeFile(file_path);
//iv_content.setImageBitmap(bitmap); // 设置图像视图的位图对象
// 第三种方式:先调用FileUtil.openImage获得位图,再调用setImageBitmap方法
Bitmap bitmap = FileUtil.openImage(file_path);
iv_content.setImageBitmap(bitmap); // 设置图像视图的位图对象

2.7 内存共享

2.7.1 ContentProvider

Android的四大组件之一,SQLite可以操作自身的数据库,而ContentProvider则是作为中间接口,通过SQLiteOpenHelper和SQLiteDatabase间接操控数据库,实现为其他应用提供数据的功能。

举例如下:

  1. 创建一个UserInfoProvider,用来提供用户信息给外界应用,在弹出的右键菜单中依次选择New→Other→Content Provider,此时会自动修改两处地方:

(1)一是在AndroidManifest.xml中添加该Provider的配置信息:

(2)二是创建的Provider会继承ContentProvider,并重写了一些方法。


public class UserInfoProvider extends ContentProvider {

    //这里是上面实现的dbHelper,用来操作本地数据库
    private UserDBHelper userDBHelper;

    //初始化
    @Override
    public boolean onCreate() {
        //初始化 dbHelper
        userDBHelper = UserDBHelper.getInstance(getContext());

        return true;
    }

    //插入
    //uri格式:content://com.example.secondandroidapp.UserInfoProvider/user
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        //使用sqlite插入数据
        SQLiteDatabase db = userDBHelper.getWritableDatabase();
        db.insert(UserDBHelper.TABLE_NAME, null, values);

        return uri;
    }

    //查询
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {

        SQLiteDatabase db = userDBHelper.getReadableDatabase();
        return db.query(UserDBHelper.TABLE_NAME, projection, selection, selectionArgs, null, null, null);
    }
    
    //删除
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int count = 0;
        switch (uriMatcher.match(uri)) {
            //这种是uri不带参数:"content://com.example.secondandroidapp.UserInfoProvider/user"
            case USER:
                // 获取SQLite数据库的写连接
                SQLiteDatabase db = userDBHelper.getWritableDatabase();
                // 执行SQLite的删除操作,并返回删除记录的数目
                count = db.delete(UserDBHelper.TABLE_NAME, selection,
                        selectionArgs);
                db.close();
                break;
            //这种是uri带参数:"content://com.example.secondandroidapp.UserInfoProvider/user/2"
            case USERS:
                String id = uri.getLastPathSegment();
                SQLiteDatabase db2 = userDBHelper.getWritableDatabase();
                count = db2.delete(UserDBHelper.TABLE_NAME, "id = ?", new String[]{id});
                db2.close();
                break;
        }
        return count;
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}
  1. 利用ContentProvider只实现服务端App的数据封装,如果客户端App想访问对方的内部数据,就要通过内容解析器ContentResolver访问。

Client的代码如下:


public class MainActivity7 extends AppCompatActivity {

    private static Uri ContentUri = Uri.parse("content://com.example.secondandroidapp.UserInfoProvider/user");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main7);

        Button insertButton = findViewById(R.id.insertButton);
        insertButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ContentValues values = new ContentValues();
                values.put("name", "陈鸿荣");
                values.put("age", "20");
                //获取到ContentResolver之后调用插入方法进行插入
                getContentResolver().insert(ContentUri, values);
            }
        });

        Button deleteButton = findViewById(R.id.deleteButton);
        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // content://com.example.secondandroidapp.UserInfoProvider/user/2
                Uri uri = ContentUris.withAppendedId(ContentUri, 2);
                int count = getContentResolver().delete(uri, null, null);
            }
        });
    }
}

出于安全考虑,Android11需要事先声明需要访问的其他应用:

在AndroidManifest.xml中添加如下:


<queries>
    <!--服务端应用包名 -->
    <package android:name="com.example.secondandroidapp"/>
    <!--或者直接指定authorities-->
    <!-- <provider android:authorities="com.example.secondandroidapp.UserInfoProvider"/>   -->
</queries>

2.7.2 使用ContentResolver读写联系人

  • 首先往raw_contacts表中插入一条数据得到id

  • 接着由于一个联系人有姓名,电话号码,邮箱,因此需要分三次插入data表中,将raw_contact_id和上面得到的id进行关联

下面是往通讯录插入和查询联系人的代码:


public class ContactActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_contact_name;
    private EditText et_contact_phone;
    private EditText et_contact_email;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contact);
        et_contact_name = findViewById(R.id.et_contact_name);
        et_contact_phone = findViewById(R.id.et_contact_phone);
        et_contact_email = findViewById(R.id.et_contact_email);
        findViewById(R.id.btn_add_contact).setOnClickListener(this);
        findViewById(R.id.btn_read_contact).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_add_contact:
                // 创建一个联系人对象
                Contact contact = new Contact();
                contact.name = et_contact_name.getText().toString().trim();
                contact.phone = et_contact_phone.getText().toString().trim();
                contact.email = et_contact_email.getText().toString().trim();

                // 方式一,使用ContentResolver多次写入,每次一个字段
//                 addContacts(getContentResolver(), contact);

                // 方式二,批处理方式
                // 每一次操作都是一个 ContentProviderOperation,构建一个操作集合,然后一次性执行
                // 好处是,要么全部成功,要么全部失败,保证了事务的一致性
                addFullContacts(getContentResolver(), contact);

                Toast.makeText(this, "添加联系人成功!", Toast.LENGTH_SHORT).show();
                break;

            case R.id.btn_read_contact:
                readPhoneContacts(getContentResolver());
                break;
        }
    }

    //往通讯录添加一个联系人信息(姓名,号码,邮箱)
    private void addContacts(ContentResolver contentResolver, Contact contact) {
        //得到rawContentId
        ContentValues values = new ContentValues();
        //插入记录得到id
        Uri uri = contentResolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
        long rawContentId = ContentUris.parseId(uri);

        //插入名字
        ContentValues name = new ContentValues();
        //关联上面得到的联系人id
        name.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);
        //关联联系人姓名的类型
        name.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
        //关联联系人姓名
        name.put(ContactsContract.Data.DATA2, contact.name);
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, name);

        //插入电话号码
        ContentValues phone = new ContentValues();
        //关联上面得到的联系人id
        phone.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);
        //关联联系人电话号码的类型
        phone.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
        //关联联系人电话号码
        phone.put(ContactsContract.Data.DATA1, contact.phone);
        //指定该号码是家庭号码还是工作号码 (家庭)
        phone.put(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, phone);

        //插入邮箱
        ContentValues email = new ContentValues();
        //关联上面得到的联系人id
        email.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);
        //关联联系人邮箱的类型
        email.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
        //关联联系人邮箱
        email.put(ContactsContract.Data.DATA1, contact.email);
        //指定该号码是家庭邮箱还是工作邮箱
        email.put(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_WORK);
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, email);
    }

    //事务操作,四个插入操作一次性提交
    private void addFullContacts(ContentResolver contentResolver, Contact contact) {
        //创建一个插入联系人主记录的内容操作器
        ContentProviderOperation op_main = ContentProviderOperation
                .newInsert(ContactsContract.RawContacts.CONTENT_URI)
                //没有实际意义,不加这个会报错(不加这个导致没有创建ContentValue,导致报错)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
                .build();
        //创建一个插入联系人姓名记录的内容操作器
        ContentProviderOperation op_name = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                //将第0个操作的id,即raw_contacts中的id作为data表中的raw_contact_id
                .withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.Data.DATA2, contact.name)
                .build();
        //创建一个插入联系人电话号码记录的内容操作器
        ContentProviderOperation op_phone = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                //将第0个操作的id,即raw_contacts中的id作为data表中的raw_contact_id
                .withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.Data.DATA1, contact.phone)
                .withValue(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
                .build();

        //创建一个插入联系人邮箱记录的内容操作器
        ContentProviderOperation op_email = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                //将第0个操作的id,即raw_contacts中的id作为data表中的raw_contact_id
                .withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.Data.DATA1, contact.email)
                .withValue(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_WORK)
                .build();

        //全部放在集合中一次性提交
        ArrayList<ContentProviderOperation> operations = new ArrayList<>();
        operations.add(op_main);
        operations.add(op_name);
        operations.add(op_phone);
        operations.add(op_email);

        try {
            //批量提交四个操作
            contentResolver.applyBatch(ContactsContract.AUTHORITY, operations);
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    //读取联系人
    @SuppressLint("Range")
    private void readPhoneContacts(ContentResolver contentResolver) {
        //先查询raw_contacts表,再根据raw_contacts_id表 查询data表
        Cursor cursor = contentResolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ContactsContract.RawContacts._ID}, null, null, null);
        while(cursor.moveToNext()){
            int rawContactId = cursor.getInt(0);
            Uri uri = Uri.parse("content://com.android.contacts/contacts/" + rawContactId + "/data");
            Cursor dataCursor = contentResolver.query(uri, new String[]{ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.Contacts.Data.DATA1, ContactsContract.Contacts.Data.DATA2}, null, null, null);
            Contact contact = new Contact();
            while (dataCursor.moveToNext()) {
                String data1 = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.Contacts.Data.DATA1));
                String mimeType = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.Contacts.Data.MIMETYPE));
                switch (mimeType) {
                    //是姓名
                    case ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE:
                        contact.name = data1;
                        break;

                    //邮箱
                    case ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE:
                        contact.email = data1;
                        break;

                    //手机
                    case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE:
                        contact.phone = data1;
                        break;
                }
            }

            dataCursor.close();

            // RawContacts 表中出现的 _id,不一定在 Data 表中都会有对应记录
            if (contact.name != null) {
                Log.d("hhh", contact.toString());
            }
        }
        cursor.close();
    }

}

2.7.3 使用ContentObserver监听短信

ContentResolver获取数据采用的是主动查询方式,有查询就有数据,没查询就没数据。ContentResolver能够实时获取新增的数据,最常见的业务场景是短信验证码。为了替用户省事,App通常会监控手机刚收到的短信验证码,并自动填写验证码输入框。这时就用到了内容观察器ContentObserver,事先给目标内容注册一个观察器,目标内容的数据一旦发生变化,就马上触发观察器的监听事件,从而执行开发者预先定义的代码。

示例代码如下:(记得在Manifest.xml中开启权限和动态开启权限)


public class MonitorSmsActivity extends AppCompatActivity {

    private SmsGetObserver mObserver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_monitor_sms);
        
        // 给指定Uri注册内容观察器,一旦发生数据变化,就触发观察器的onChange方法
        Uri uri = Uri.parse("content://sms");
        
        // notifyForDescendents:
        // false :表示精确匹配,即只匹配该Uri,true :表示可以同时匹配其派生的Uri
        mObserver = new SmsGetObserver(this);
        getContentResolver().registerContentObserver(uri, true, mObserver);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消注册
        getContentResolver().unregisterContentObserver(mObserver);
    }

    private static class SmsGetObserver extends ContentObserver {

        private final Context mContext;

        public SmsGetObserver(Context context) {
            super(new Handler(Looper.getMainLooper()));
            this.mContext = context;
        }

        //回调
        @SuppressLint("Range")
        @Override
        public void onChange(boolean selfChange, @Nullable Uri uri) {
            super.onChange(selfChange, uri);
            // onChange会多次调用,收到一条短信会调用两次onChange
            // mUri===content://sms/raw/20
            // mUri===content://sms/inbox/20
            // 安卓7.0以上系统,点击标记为已读,也会调用一次
            // mUri===content://sms
            // 收到一条短信都是uri后面都会有确定的一个数字,对应数据库的_id,比如上面的20
            if (uri == null) {
                return;
            }
            if (uri.toString().contains("content://sms/raw") ||
                    uri.toString().equals("content://sms")) {
                return;
            }

            // 通过内容解析器获取符合条件的结果集游标
            Cursor cursor = mContext.getContentResolver().query(uri, new String[]{"address", "body", "date"}, null, null, "date DESC");
            if (cursor.moveToNext()) {
                // 短信的发送号码
                String sender = cursor.getString(cursor.getColumnIndex("address"));
                // 短信内容
                String content = cursor.getString(cursor.getColumnIndex("body"));
                Log.d("ning", String.format("sender:%s,content:%s", sender, content));
            }
            cursor.close();
        }
    }
}

2.8 几种常用的弹框

2.8.1 SweetAlertDialog弹框

添加依赖


implementation 'com.github.f0ris.sweetalert:library:1.5.1'

下面是具体用法:


new SweetAlertDialog(this, SweetAlertDialog.WARNING_TYPE)
                .setTitleText("提示")
                .setContentText("是否退出系统")
                .setConfirmText("确定")
                .setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
                    @Override
                    public void onClick(SweetAlertDialog sweetAlertDialog) {
                        sweetAlertDialog.cancel();
                       
                    }
                })
                .setCancelText("取消")
                .setCancelClickListener(new SweetAlertDialog.OnSweetClickListener() {
                    @Override
                    public void onClick(SweetAlertDialog sweetAlertDialog) {
                        sweetAlertDialog.dismiss();
                    }
                })
                .show();

该弹框在提示内容字数过多的时候好像有问题???字数太多就显示不完整了

2.8.2 AlertDialog 弹框


AlertDialog alertDialog1 = new AlertDialog.Builder(AtlasActivity.this)
                            .setTitle("提示")//标题
                            .setMessage(obj)//内容
                            .setIcon(R.mipmap.logo)//图标
                            .setCancelable(false) //点击弹框外部不会消失
                            .setPositiveButton("确定",new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialogInterface, int i) {
                                    dialogInterface.cancel();
                                }
                            })
                            .setNegativeButton("关闭", new DialogInterface.OnClickListener() {//添加取消
                                @Override
                                public void onClick(DialogInterface dialogInterface, int i) {
                                    dialogInterface.cancel();
                                }
                            })
                            .create();
                    alertDialog1.show();

该弹框可以自定义图标,也可以显示较多的内容信息,感觉不错。

弹框也可以添加其他的页面:

先获取页面:


View v = LayoutInflater.from(AtlasActivity.this).inflate(R.layout.atlas_audit_information, null);

向弹框中添加页面只需:


.setView(v);

2.9 Date与时间戳的相互转换

1、Date对象转换为时间戳


Date date = new Date();  
long times = date.getTime();  
System.out.println(times);      
1508824283292

2、时间戳转换为Date日期对象


long times = System.currentTimeMillis();  
Date date = new Date(times);  
System.out.println(date); Tue Oct 24 13:49:28 CST 2017

3、时间戳转换为指定日期格式


SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long times = System.currentTimeMillis(); 
String str = format.format(times);
System.out.println(str);2017-10-24 13:50:46     

4、时间字符串<年月日时分秒毫秒 >转为 时间戳

20180914150324转为1536908604990


//大写HH:24小时制,小写hh:12小时制
//毫秒:SSS
//指定转化前的格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
//转化后为Date日期格式
Date date = sdf.parse(sb.toString());
//Date转为时间戳long
long shootTime = date.getTime();
System.out.println(shootTime);

5、oldValue是一个时间戳字符串String 转为 long 转为 时间


 newValue = ext.get("inUnitTime").toString();
 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
 oldValue = sdf.format(new Date(Long.valueOf(oldValue+"000")));


   public String getFormDate(String timeStamp){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(new Date(Long.valueOf(timeStamp+"000")));
    }

三、常用组件

3.1 碎片组件 Fragment

Fragment(碎片)是一种可以嵌入在Activity中的UI片段,与Activity非常相似,不仅包含布局,同时也具有自己的生命周期。

Fragment 表示应用界面中可重复使用的一部分。Fragment 允许您将界面划分为离散的区块,从而将模块化和可重用性引入 Activity 的界面。

Fragment的布局文件和代码使用起来和Activity基本无异。除了继承自Fragment与入口方法onCreateView两点,其他地方类似活动页面代码。

Fragment的注册方式有两种:

  • 静态注册:在xml中引入


public class StaticFragment extends Fragment {//创建一个Frament
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_static, container, false);
    }
}
public class FragmentStaticActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment_static);
    }
}

//Fragment的布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:background="#bbffbb">

    <TextView
        android:id="@+id/tv_adv"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:text="广告图片"
        android:textColor="#000000"
        android:textSize="17sp" />

    <ImageView
        android:id="@+id/iv_adv"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="4"
        android:src="@drawable/adv"
        android:scaleType="fitCenter" />
</LinearLayout>

//在Activity的布局文件中静态引入Fragment
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FragmentStaticActivity"
    android:orientation="vertical">
    
    <!-- 引入Fragment-->
    <fragment
        android:id="@+id/fragment_static"
        android:name="com.example.gaojikongjian.fragment.StaticFragment"
        android:layout_width="match_parent"
        android:layout_height="60dp"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="这里是每个页面的具体内容"
        android:textColor="#000000"
        android:textSize="17sp"/>
</LinearLayout>
  • 动态注册:通过java代码的方式引入


//创建Fragment
public class DynamicFragment extends Fragment {

    public static DynamicFragment newInstance(int position, int image_id, String desc) {
        DynamicFragment fragment = new DynamicFragment();
        //把参数打包,传入Fragment中
        Bundle args = new Bundle();
        args.putInt("position", position);
        args.putInt("image_id", image_id);
        args.putString("desc", desc);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        //根据布局文件生成视图对象
        View view = inflater.inflate(R.layout.fragment_dynamic, container, false);
        Bundle arguments = getArguments();

        if(arguments != null){
            ImageView iv_pic = view.findViewById(R.id.iv_pic);
            TextView tv_desc = view.findViewById(R.id.tv_desc);
            iv_pic.setImageResource(arguments.getInt("image_id", R.drawable.huawei));
            tv_desc.setText(arguments.getString("desc"));
        }

        return view;
    }
}

//Fragment的布局文件 fragment_dynamic
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv_pic"
        android:layout_width="match_parent"
        android:layout_height="360dp"
        android:scaleType="fitCenter" />

    <TextView
        android:id="@+id/tv_desc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="left"
        android:textColor="@color/black"
        android:textSize="17sp" />

</LinearLayout>

//适配器 MobilePagerAdapter
public class MobilePagerAdapter extends FragmentPagerAdapter {


    private List<GoodsInfo> mGoodsList;

    public MobilePagerAdapter(@NonNull FragmentManager fm, List<GoodsInfo> goodsList) {
        super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        this.mGoodsList = goodsList;
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {

        GoodsInfo goodsInfo = mGoodsList.get(position);

        return DynamicFragment.newInstance(position, goodsInfo.pic, goodsInfo.description);
    }

    @Override
    public int getCount() {
        return mGoodsList.size();
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return mGoodsList.get(position).name;
    }
}
//Activity
public class FragmentDynamicActivity extends AppCompatActivity {

    private ArrayList<GoodsInfo> mGoodsList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment_dynamic);

        initPagerStrip();
        initViewPager();
    }

    // 初始化翻页标签栏
    private void initPagerStrip() {
        PagerTabStrip pts_tab = findViewById(R.id.pts_tab);
        // 设置翻页标签栏的文本大小
        pts_tab.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
        pts_tab.setTextColor(Color.BLACK);
    }

    // 初始化翻页视图
    private void initViewPager() {
        ViewPager vp_content = findViewById(R.id.vp_content);
        mGoodsList = GoodsInfo.getDefaultList();
        //适配器
        MobilePagerAdapter adapter = new MobilePagerAdapter(getSupportFragmentManager(), mGoodsList);
        vp_content.setAdapter(adapter);
        vp_content.setCurrentItem(3);
    }
}

//Activity的布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/vp_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.viewpager.widget.PagerTabStrip
            android:id="@+id/pts_tab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </androidx.viewpager.widget.ViewPager>

</LinearLayout>

3.2 广播组件 Broadcast

广播组件 Broadcast 是Android 四大组件之一。

广播有以下特点:

  • 活动只能一对一通信;而广播可以一对多,一人发送广播,多人接收处理。

  • 对于发送方来说,广播不需要考虑接收方有没有在工作,接收方在工作就接收广播,不在工作就丢弃广播。

  • 对于接收方来说,因为可能会收到各式各样的广播,所以接收方要自行过滤符合条件的广播,之后再解包处理。

与广播有关的方法主要有以下3个。

  • sendBroadcast:发送广播。

  • registerReceiver:注册广播的接收器,可在onStart或onResume方法中注册接收器。

  • unregisterReceiver:注销广播的接收器,可在onStop或onPause方法中注销接收器。

四、架构

4.1 mvvm mvp mvc参考

Android MVC、MVP、MVVM架构 - 简书

五、MVVMHabit框架学习

MVVMHabit: 基于谷歌最新AAC架构,MVVM设计模式的一套快速开发库,整合Okhttp+RxJava+Retrofit+Glide等主流模块,满足日常开发需求。使用该框架可以快速开发一个高质量、易维护的Android应用。 (gitee.com)

retrofit+okhttp+rxJava负责网络请求;gson负责解析json数据;glide负责加载图片;rxlifecycle负责管理view的生命周期;与网络请求共存亡;rxbinding结合databinding扩展UI事件;rxpermissions负责Android 6.0权限申请;material-dialogs一个漂亮的、流畅的、可定制的material design风格的对话框。

5.1 全屏显示


@Override
public void initParam()方法中
// activity中全屏        
getActivity().requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题
        getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置全屏
        getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// fragment中全屏
        getActivity().getWindow()
                .setFlags(
                        WindowManager.LayoutParams.FLAG_FULLSCREEN,
                        WindowManager.LayoutParams.FLAG_FULLSCREEN);

5.2 横屏显示


@Override
    protected void onResume() {
        /**
         * 设置为横屏
         */
        if(getRequestedOrientation()!=ActivityInfo.SCREEN_ORIENTATION_PORTRAIT){
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
        super.onResume();
    }

5.3 TabLayout标签

5.3.1 app:tabMode和app: tabGravity配合使用效果对比

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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context="com.louisgeek.louistabgravityandtabmode.MainActivity">
<!--所有  TabLayout  layout_width设置为match_parent的情况下-->
 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="标题想数目数量多的时候"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="tabGravity:fill,tabMode:fixed"
        />
    <android.support.design.widget.TabLayout
        android:id="@+id/id_tab1_fill_fixed"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="fill"
        app:tabMode="fixed"
        />
 
 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="tabGravity:center,tabMode:fixed"
        />
    <android.support.design.widget.TabLayout
        android:id="@+id/id_tab2_center_fixed"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="center"
        app:tabMode="fixed"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="tabGravity:fill,tabMode:scrollable"
        />
    <android.support.design.widget.TabLayout
        android:id="@+id/id_tab3_fill_scrollable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="fill"
        app:tabMode="scrollable"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="tabGravity:center,tabMode:scrollable常用"
        android:textColor="@color/colorAccent"
        />
    <android.support.design.widget.TabLayout
        android:id="@+id/id_tab4_center_scrollable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="center"
        app:tabMode="scrollable"
        />
 
    <!--///-->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="标题想数目数量少的时候"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="tabGravity:fill,tabMode:fixed"
        />
    <android.support.design.widget.TabLayout
        android:id="@+id/id_tab_one_fill_fixed"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="fill"
        app:tabMode="fixed"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="tabGravity:center,tabMode:fixed"
        />
    <android.support.design.widget.TabLayout
        android:id="@+id/id_tab_two_center_fixed"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="center"
        app:tabMode="fixed"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="tabGravity:fill,tabMode:scrollable"
        />
    <android.support.design.widget.TabLayout
        android:id="@+id/id_tab_three_fill_scrollable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="fill"
        app:tabMode="scrollable"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="tabGravity:center,tabMode:scrollable常用"
        android:textColor="@color/colorAccent"
        />
    <android.support.design.widget.TabLayout
        android:id="@+id/id_tab_four_center_scrollable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="center"
        app:tabMode="scrollable"
        />

</LinearLayout>

package com.louisgeek.louistabgravityandtabmode;
 
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v7.app.AppCompatActivity;
 
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
 
        
        TabLayout idtablone = (TabLayout) findViewById(R.id.id_tab1_fill_fixed);
        idtablone.addTab(idtablone.newTab().setText("标题"));
        idtablone.addTab(idtablone.newTab().setText("标题文字"));
        idtablone.addTab(idtablone.newTab().setText("标题党"));
        idtablone.addTab(idtablone.newTab().setText("题"));
        idtablone.addTab(idtablone.newTab().setText("标题1"));
        idtablone.addTab(idtablone.newTab().setText("标题2"));
        idtablone.addTab(idtablone.newTab().setText("标题3"));
        idtablone.addTab(idtablone.newTab().setText("标题4"));
        idtablone.addTab(idtablone.newTab().setText("标题5"));
        idtablone.addTab(idtablone.newTab().setText("标题6"));
 
 
        TabLayout idtabltwo = (TabLayout) findViewById(R.id.id_tab2_center_fixed);
 
        idtabltwo.addTab(idtabltwo.newTab().setText("标题"));
        idtabltwo.addTab(idtabltwo.newTab().setText("标题文字"));
        idtabltwo.addTab(idtabltwo.newTab().setText("标题党"));
        idtabltwo.addTab(idtabltwo.newTab().setText("题"));
        idtabltwo.addTab(idtabltwo.newTab().setText("标题1"));
        idtabltwo.addTab(idtabltwo.newTab().setText("标题2"));
        idtabltwo.addTab(idtabltwo.newTab().setText("标题3"));
        idtabltwo.addTab(idtabltwo.newTab().setText("标题4"));
        idtabltwo.addTab(idtabltwo.newTab().setText("标题5"));
        idtabltwo.addTab(idtabltwo.newTab().setText("标题6"));
 
        TabLayout idtablthree = (TabLayout) findViewById(R.id.id_tab3_fill_scrollable);
 
        idtablthree.addTab(idtablthree.newTab().setText("标题"));
        idtablthree.addTab(idtablthree.newTab().setText("标题文字"));
        idtablthree.addTab(idtablthree.newTab().setText("标题党"));
        idtablthree.addTab(idtablthree.newTab().setText("题"));
        idtablthree.addTab(idtablthree.newTab().setText("标题1"));
        idtablthree.addTab(idtablthree.newTab().setText("标题2"));
        idtablthree.addTab(idtablthree.newTab().setText("标题3"));
        idtablthree.addTab(idtablthree.newTab().setText("标题4"));
        idtablthree.addTab(idtablthree.newTab().setText("标题5"));
        idtablthree.addTab(idtablthree.newTab().setText("标题6"));
 
        TabLayout idtablfour = (TabLayout) findViewById(R.id.id_tab4_center_scrollable);
 
        idtablfour.addTab(idtablfour.newTab().setText("标题"));
        idtablfour.addTab(idtablfour.newTab().setText("标题文字"));
        idtablfour.addTab(idtablfour.newTab().setText("标题党"));
        idtablfour.addTab(idtablfour.newTab().setText("题"));
        idtablfour.addTab(idtablfour.newTab().setText("标题1"));
        idtablfour.addTab(idtablfour.newTab().setText("标题2"));
        idtablfour.addTab(idtablfour.newTab().setText("标题3"));
        idtablfour.addTab(idtablfour.newTab().setText("标题4"));
        idtablfour.addTab(idtablfour.newTab().setText("标题5"));
        idtablfour.addTab(idtablfour.newTab().setText("标题6"));
 
        //
 
        TabLayout id_tabl_one_s = (TabLayout) findViewById(R.id.id_tab_one_fill_fixed);
        id_tabl_one_s.addTab(id_tabl_one_s.newTab().setText("标1"));
        id_tabl_one_s.addTab(id_tabl_one_s.newTab().setText("标2"));
        id_tabl_one_s.addTab(id_tabl_one_s.newTab().setText("标3"));
        id_tabl_one_s.addTab(id_tabl_one_s.newTab().setText("标4"));
        id_tabl_one_s.addTab(id_tabl_one_s.newTab().setText("标5"));
      /* id_tabl_one_s.addTab(id_tabl_one_s.newTab().setText("标6"));
       id_tabl_one_s.addTab(id_tabl_one_s.newTab().setText("标7"));*/
 
 
        TabLayout id_tabl_two_s = (TabLayout) findViewById(R.id.id_tab_two_center_fixed);
        id_tabl_two_s.addTab(id_tabl_two_s.newTab().setText("标1"));
        id_tabl_two_s.addTab(id_tabl_two_s.newTab().setText("标2"));
        id_tabl_two_s.addTab(id_tabl_two_s.newTab().setText("标3"));
        id_tabl_two_s.addTab(id_tabl_two_s.newTab().setText("标4"));
        id_tabl_two_s.addTab(id_tabl_two_s.newTab().setText("标5"));
        /*id_tabl_two_s.addTab(id_tabl_two_s.newTab().setText("标5"));
        id_tabl_two_s.addTab(id_tabl_two_s.newTab().setText("标6"));
        id_tabl_two_s.addTab(id_tabl_two_s.newTab().setText("标7"));*/
 
 
        TabLayout id_tabl_three_s = (TabLayout) findViewById(R.id.id_tab_three_fill_scrollable);
        id_tabl_three_s.addTab(id_tabl_three_s.newTab().setText("标1"));
        id_tabl_three_s.addTab(id_tabl_three_s.newTab().setText("标2"));
        id_tabl_three_s.addTab(id_tabl_three_s.newTab().setText("标3"));
        id_tabl_three_s.addTab(id_tabl_three_s.newTab().setText("标4"));
        id_tabl_three_s.addTab(id_tabl_three_s.newTab().setText("标5"));
        /*id_tabl_three_s.addTab(id_tabl_three_s.newTab().setText("标6"));
        id_tabl_three_s.addTab(id_tabl_three_s.newTab().setText("标7"));*/
 
        TabLayout id_tabl_four_s = (TabLayout) findViewById(R.id.id_tab_four_center_scrollable);
        id_tabl_four_s.addTab(id_tabl_four_s.newTab().setText("标1"));
        id_tabl_four_s.addTab(id_tabl_four_s.newTab().setText("标2"));
        id_tabl_four_s.addTab(id_tabl_four_s.newTab().setText("标3"));
        id_tabl_four_s.addTab(id_tabl_four_s.newTab().setText("标4"));
        id_tabl_four_s.addTab(id_tabl_four_s.newTab().setText("标5"));
       /* id_tabl_four_s.addTab(id_tabl_four_s.newTab().setText("标6"));
        id_tabl_four_s.addTab(id_tabl_four_s.newTab().setText("标7"));*/
 
 
    }
}

5.3.2参考文档

Android快速开发库之TabLayout - 简书

GitHub - bo980/JTabLayout

5.4 TwinklingRefreshLayout刷新标签

下拉刷新框架TwinklingRefreshLayout的使用 - 简书

https://github.com/lcodecorex/TwinklingRefreshLayout/blob/master/README_CN.md

5.5 网络请求 Retrofit+Okhttp+RxJava

Retrofit + RxJava + OkHttp 让网络请求变的简单-基础篇 - 简书

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值