一、文件创建目录结构
-
工程项目创建完成后,默认打开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 权限获取与回调
-
检查权限时,在机器上弹出选择框,当你选择完是否获取权限后,执行onRequestPermissionResult回调函数,获取选择的结果。
-
机器弹出获取权限选择框,这时如果继续执行检查权限后的语句,不会等待权限是否获取,容易出现问题,所以一般是在执行onRequestPermissionResult之后确认获取权限的位置之后,再进行相关函数的执行。
-
权限获取示例,这里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申请失败");
}
}
-
蓝牙钥匙主页面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的方式不同,有三种:
-
从指定资源文件中获取:decodeResource,例如从资源文件img.png获取位图对象:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img);
-
从指定路径下获取:decodeFile,但是要注意从Android10开始,该方法只能获取私有空间下的图片,公共空间下获取不了。
Bitmap bitmap = BitmapFactory.decodeFile("C:\\Users\\OYMN\\Pictures\\onepunch.jpg");
-
从指定的输入流中获取,比如使用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间接操控数据库,实现为其他应用提供数据的功能。
举例如下:
-
创建一个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");
}
}
-
利用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参考
五、MVVMHabit框架学习
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参考文档
5.4 TwinklingRefreshLayout刷新标签
下拉刷新框架TwinklingRefreshLayout的使用 - 简书
https://github.com/lcodecorex/TwinklingRefreshLayout/blob/master/README_CN.md