您现在的位置是:网站首页> Android
Android快速回顾
- Android
- 2022-03-10
- 1516人已阅读
从安卓6开始,部分权限需要动态申请,这种叫动态权限,这种申请到的权限也可以动态撤销,就是要求程序再次执行代码去向系统申请权限,比如拍照、存储读写等。也不是所有的权限都改成了动态申请,意味着兼容安卓6以上的系统你既要在AndroidManifest.xml中写上要求的权限,也要通过checkPermission申请你需要的权限
Android开发约束布局ConstraintLayout学习总结
android.mk 和application.mk 详细分析
原生Material UI框架的使用
目前AndroidStuido默认已使用Material Design
当使用Android原生开发并使用Material Design时,可以通过以下步骤来创建一个使用Material Design的简单示例:
首先,确保你的Android项目已经引入了Material Components库。可以在项目的build.gradle文件中添加以下依赖项:
implementation 'com.google.android.material:material:1.4.0'
在你的布局文件中使用Material Design的UI组件。例如,可以使用com.google.android.material.button.MaterialButton来创建一个Material风格的按钮:
xml
复制
<com.google.android.material.button.MaterialButton
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me"
app:icon="@drawable/ic_baseline_add_24"
app:iconGravity="textStart"
app:cornerRadius="8dp"
app:backgroundTint="@color/material_blue"
app:rippleColor="@color/material_light_blue"
/>
在上面的示例中,我们使用了MaterialButton,并设置了一些属性,如文本、图标、圆角半径、背景色和点击效果等。
在Activity或Fragment中使用该布局文件,并处理按钮的点击事件。例如,在Activity的onCreate方法中:
public class MainActivity extends AppCompatActivity {
private MaterialButton button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 处理按钮点击事件
Toast.makeText(MainActivity.this, "Button Clicked", Toast.LENGTH_SHORT).show();
}
});
}
}
在上述代码中,我们通过findViewById获取到按钮实例,并为按钮设置了点击事件监听器,在点击时显示一个简单的Toast消息。
通过以上步骤,你可以在Android原生开发中使用Material Design框架创建具有漂亮的UI风格的应用程序。这只是一个简单的示例,Material Design还提供了许多其他的UI组件和样式
仓库
android动态权限获取
Android6.0采用新的权限模型,只有在需要权限的时候,才告知用户是否授权,是在runtime时候授权,而不是在原来安装的时候 ,同时默认情况下每次在运行时打开页面时候,需要先检查是否有所需要的权限申请。这样的用户的自主性提高很多,比如用户可以给APP赋予摄像的权限,但是可以拒绝记录设备位置的权限,就是怕位置信息上传等等。
PROTECTION_NORMAL类权限
当用户安装或更新应用时,系统将授予应用所请求的属于 PROTECTION_NORMAL 的所有权限(安装时授权的一类基本权限)。这类权限包括:
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
只需要在AndroidManifest.xml中简单声明这些权限就好,安装时就授权。不需要每次使用时都检查权限,而且用户不能取消以上授权。
权限组
新的权限模型中还提出了一个权限组的概念,可以简单理解为如果一个权限组内的某个权限被获取了,那么这个组中剩余的权限也会被自动获取。
例如:Android.permission-group.CALENDAR中的android.permission.WRITE_CALENDAR 权限被获取,那么应用会自动获取android.permission.READ_CALENDAR权限。
动态权限获取:
判断是否已经拥有某项权限,如果没有则申请该权限。 并通过重载onRequestPermissionsResult()函数,监听权限申请的结果。通过requestCode判断是那一次的权限申请。
grantResult[0] 是否等于PackageManager.PERMISSION_GRANTED来判断 权限获取的成功与否。
private void insertDummyContactWrapper() {
int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_ASK_PERMISSIONS);
return;
}
insertDummyContact();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 123:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted
Toast.makeText(this,"权限申请成功",Toast.LENGTH_SHORT).show();
} else {
// Permission Denied
Toast.makeText(MainActivity.this, "权限申请失败", Toast.LENGTH_SHORT)
.show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
requestPermission中第二个参数,为请求码。
shouldShowRequestPermissionRationale :
安装app后第一次调用权限申请: 返回false
第一次 禁止权限但是没有勾选下次不再提醒: 返回true
如果第一次禁止权限并且勾选不再提醒: 返回 false
一般通过此函数,当返回true时 ,弹出对话框提醒用户 该权限的作用
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Explain to the user why we need to read the contacts
showMessage("请允许应用对SD卡进行读写操作",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
Toast.makeText(this,"该权限必须获得才能实现某功能,请开通",LENGTH_SHORT).show;
}
});
return;
}
requestPermissions(
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
REQUEST_CODE_ASK_PERMISSIONS);
}
private void showMessage(String message,
DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(MainActivity.this).setMessage(message)
.setPositiveButton("OK", okListener).create().show();
}
用兼容库使代码兼容旧版
当把 minSDK设置成 13 targetSDK设置成 25时,不能调用requestPermission函数,报错:call requires api level 23 。
为了兼容minSDK 23以下在调用以上这三个接口时,可以增加一个判断:
if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.M )
requestPermissions(new String[] {Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_CODE);
这样做非常麻烦,我们可以使用 v4兼容库,已对这个做过兼容,用以下方法代替:
复制代码
ContextCompat.checkSelfPermission()
被授权函数返回PERMISSION_GRANTED,否则返回PERMISSION_DENIED ,在所有版本都是如此。
ActivityCompat.requestPermissions()
这个方法在M之前版本调用,OnRequestPermissionsResultCallback 直接被调用,带着正确的 PERMISSION_GRANTED或者 PERMISSION_DENIED 。
ActivityCompat.shouldShowRequestPermissionRationale()
在M之前版本调用,永远返回false。
用v4包的这三方法,完美兼容所有版本!这个方法需要额外的参数,Context or Activity。别的就没啥特别的了。下面是代码:
Android Studio引用本地jar包的方法:
1. 目录结构选择“Project
2. 把jar包直接复制到libs目录, 选中它,右击,选择 Add As Library
3. 在app/build.gradle中,观察是否有如下内容:
compile files('libs/commons-math3-3.6.1.jar')
完成。
如何将android apk编译为arr包
将应用模块转换为库模块
如果现有的应用模块包含您想要重复使用的所有代码,您可以按照以下步骤将其转换为库模块:
打开模块级 build.gradle 文件。
删除 applicationId 行。只有 Android 应用模块才能定义此行。
在文件的顶部,您应该会看到以下代码:
apply plugin: 'com.android.application'
将其更改为以下代码:
apply plugin: 'com.android.library'
保存文件,然后依次点击 File > Sync Project with Gradle Files。
大功告成。模块的整个结构仍然相同,但是现在它会作为 Android 库运行,编译现在也会创建 AAR 文件,而不是 APK。
如果您想要编译 AAR 文件,请在 Project 窗口中选择库模块,然后依次点击 Build > Build APK
编译Release的aar
更新UI
final String res = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
contentTv.setText(res);
}
});
Android引用arr包的两种方法
android中引用的包一般分为两种:
1、jar包
2、arr包
arr包其实带有res的jar包,而普通的jar包是不带资源文件的。那么如何在项目中引用呢?
1、将arr包复制到libs目录下
2、在build.gradle文件中添加一个本地仓库,并把libs目录作为仓库的地址。
repositories {flatDir {dirs 'libs'}}
android{
.
.
.
repositories {
flatDir {
dirs 'libs'
}
}
.
.
.
}
3、在build.gradle文件中dependencies标签中添加下面的依赖
dependencies {
api fileTree(dir: 'libs', include: '*.jar')
implementation (name:'你的aar名字', ext:'aar')
}
Android studio 导出,导入 arr包
arr包与jar包不同之处在于arr可以导入.class文件与其资源文件.因为是使用依赖完成导入所以不需要配置gradle。
导出arr包
创建module(模块)
打开Flie–>New –>NewModule
选择Android Libray
修改命名,然后finish
创建成功后
创建成功后即可在module中编写代码
注:module中不能使用switch语句,因为Module中生成的R.java中的资源ID不是常数,ID没有final修饰词
导出arr包
导出arr包有两种方法
方法一:
是Build–>Rebuild Project
方法二:
使用Terminal
在Terminal中输入 gradlew assembleRelease(windows)
?????????./gradlew assembleRelease(linux)
编译完成后arr包的位置在,你的module–>Build–>output–>arr文件夹下
这里写图片描述
导出arr包结束
导入arr包
创建module(模块)
将准备导入的arr包放在app–>libs 文件夹下
打开Flie–>New –>NewModule
这次选择Imoprt.JAR/.AAR Package
文件选择在lib中的arr包
成功后会在项目里创建一个module
Rebuild 一下项目
Build–>Rebuild Project
成功后会多一个iml文件
打开File–>Project Structure…
点击app,找到Dependencies,点击“+”选择Module?dependency
选择你的arr包生成的module,点击OK
当然如果不想使用Project Structure… 也可在你的build.gradle中配置,只需一行代码
dependencies {
……
compile project(‘:你的arr包名’)
}
最后查看project目录–>External Libraries
如果有以上文件说明导入成功,可以在project中直接引用了
注:build.gradle中一定要有
???? ?compile fileTree(include: [‘*.jar’], dir: ‘libs’)这句代码
Android快速回顾
android:layout_margin就是设置view的上下左右边框的额外空间
android:padding是设置内容相对view的边框的距离
在LinearLayout、RelativeLayout、TableLayout中,这2个属性都是设置都是有效的
在FrameLayout中,android:layout_margin是无效的,因为FrameLayout里面的元素都是从左上角开始绘制的
在AbsoluteLayout中,没有android:layout_margin属性
代码设置组件位置
//这里我用FrameLayout布局为列,其他布局设置方法一样,只需改变布局名就行
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) photoView.getLayoutParams();
//设置控件的宽高
params.width = previewWidth;
params.height = previewHeight/2;
//设置各个方向上的间距
params.setMargins(0,previewHeight/2,0,0);
//改变控件的属性
photoView.setLayoutParams(params);
组件显示隐藏
基本概念
1、在xml文件中对控件可进行配置
2、在Java代码中进行设置
可见:
android:visibility="visible";
Java代码:view.setVisibility(View.VISIBLE);
不可见:
android:visibility="invisible";
Java代码:view.setVisibility(View.INVISIBLE);
隐藏:
android:visibility="gone";
Java代码:view.setVisibility(View.GONE);
注意:invisible--不显示,但保留所占的空间;visible--正常显示;gone:不显示,且不保留所占的空间
如,一个场景--点击button之后,该按钮进行显示
XML里:
android:visibility="visible"
代码里,如Button
btn.setVisibility(View.VISIBLE);
我在项目当中用到的是:
1、布局中设置控件为android:visibility
2、在程序中可用setVisibility();
3、对应的三个常量值为0、4、8
VISIBLE:0 可见的
INVISIBLE:4 不可见的,但还占着原来的空间
GONE:8 隐藏,不占用原来的布局空间
当用setVisibility();该方法设置控件隐藏或显示时,该方法内对应的取值为int类型,所以可取常量值
AndroidManifest.xml
典型
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="cn.gavinliu.notificationbox"
android:versionCode="1"
android:versionName="1.0"
>
<!--配置权限区域-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- for umeng -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<!-- X5 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 硬件加速对X5视频播放非常重要,建议开启 -->
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- 开机启动所需权限 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!--配置权限区域结束-->
<!-- x5 -->
<application
android:name=".NotificationBoxApp"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="false"
android:theme="@style/AppTheme"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:icon, android:theme"
>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
<activity
android:name=".ui.main.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme"></activity>
<receiver android:name="cn.gavinliu.notificationbox.update_app.wcl_update_app.receivers.InstallReceiver" >
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
<receiver android:name="cn.gavinliu.notificationbox.library.ContentReceiver"
>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
<service
android:name=".service.NotificationListenerService"
android:label="@string/service_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:persistent="true"
android:process=":remote">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<!--<service-->
<!--android:name=".service.ScService"-->
<!--android:label="ScService"-->
<!--android:enabled="true"-->
<!--android:exported="true">-->
<!--<intent-filter>-->
<!--<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />-->
<!--</intent-filter>-->
<!--</service>-->
<service android:name=".service.MyServicetwo"
android:process=":live"
></service>
<service android:name=".service.MyService"
></service>
<service android:name=".service.LoginService"
></service>
<service android:name=".service.MyServicelunxun"
></service>
<service android:name=".x5webviewtwo.X5Serviece"
></service>
<activity
android:name=".ui.applist.AppListActivity"
android:label="@string/activity_applist" />
<activity android:name=".ui.detail.DetailActivity" />
<activity
android:name=".ui.setting.SettingActivity"
android:label="@string/action_settings" />
<activity
android:name=".StartActivity"
android:theme="@style/Theme.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".LoginActivity"
android:theme="@style/Theme.Splash">
</activity>
<activity android:name=".library.PixelActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
>
</activity>
</application>
</manifest>
<intent-filter>元素
指明这个activity可以以什么样的意图(intent)启动。该元素有几个子元素可以包含。我们先介绍遇到的这两个:
<action>元素
表示activity作为一个什么动作启动,android.intent.action.MAIN表示作为主activity启动。
<category>元素
这是action元素的额外类别信息,android.intent.category.LAUNCHER表示这个activity为当前应用程序优先级最高的Activity。
资源文本 如
android:text="@string/btn"
布局
LinearLayout 线性布局
gravity 内部元素对齐方式 bottom center top left right center_vertical center_horizontal ...
orientation 内部部件的排列方式 horizontal vertical
layout_width:wrap_content match_parent
内部子元素自己的附加属性
layout_gravity 对齐方式
layout_weight 权重
TableLayout 表格布局
collapseColumns 设置被隐藏的列序列号 多个序列号
shrinkColumns 设置允许被收缩的列序号 多个序列号
stretchColumns 设置允许被拉伸的列序号 多个序列号
内部包含
<TableRow>
单元格属性: 单元格属性有两个属性
Android:layout_column 指定该单元格在第几列显示
Android:layout_span 指定该单元格占据的列数(如果我们在使用中没有指定,那么默认值将为1)
下面就来举例说明一下:
Android:layout_column="1" 该控件在第1列
Android:layout_span="2" 该控件占了2列
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="3dip"
>
<!-- 第1个TableLayout,用于描述表中的列属性。第0列可伸展,第1列可收缩 ,第2列被隐藏-->
<TextView
android:text="第一个表格:全局设置:列属性设置"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textSize="15sp"
android:background="#7f00ffff"/>
<TableLayout
android:id="@+id/table1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="0"
android:shrinkColumns="1"
android:collapseColumns="2"
android:padding="3dip">
<TableRow>
<Button android:text="该列可以伸展"/>
<Button android:text="该列可以收缩"/>
<Button android:text="被隐藏了"/>
</TableRow>
<TableRow>
<TextView android:text="向行方向伸展,可以伸展很长 "/>
<TextView android:text="向列方向收缩,*****************************************************************************************可以伸缩很长"/>
</TableRow>
</TableLayout>
<!-- 第2个TableLayout,用于描述表中单元格的属性,包括:android:layout_column 及android:layout_span-->
<TextView
android:text="第二个:单元格设置:指定单元格属性设置"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textSize="15sp"
android:background="#7f00ffff"/>
<TableLayout
android:id="@+id/table2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="3dip">
<TableRow>
<Button android:text="第1列"/>
<Button android:text="第2列"/>
<Button android:text="第3列"/>
</TableRow>
<TableRow>
<TextView android:text="指定在第2列" android:layout_column="1"/>
</TableRow>
<TableRow>
<TextView
android:text="第二列和第三列!!!!!!!!!!!!"
android:layout_column="1"
android:layout_span="2"
/>
</TableRow>
</TableLayout>
<!-- 第3个TableLayout,使用可伸展特性布局-->
<TextView
android:text="第三个表格:非均匀布局,控件长度根据内容伸缩"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textSize="15sp"
android:background="#7f00ffff"/>
<TableLayout
android:id="@+id/table3"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="*"
android:padding="3dip"
>
<TableRow>
<Button android:text="一笑山河红袖遮" ></Button>
<Button android:text="姜泥"></Button>
<Button android:text="两剑惊破旧山河" ></Button>
</TableRow>
</TableLayout>
<!-- 第4个TableLayout,使用可伸展特性,并指定每个控件宽度一致,如1dip-->
<TextView
android:text="表4:均匀布局,控件宽度一致"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textSize="15sp"
android:background="#7f00ffff"/>
<TableLayout
android:id="@+id/table4"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="*"
android:padding="3dip"
>
<TableRow>
<Button android:text="徐凤年" android:layout_width="1dip"></Button>
<Button android:text="温华" android:layout_width="1dip"></Button>
<Button android:text="天不生我李淳罡" android:layout_width="1dip"></Button>
</TableRow>
</TableLayout>
</LinearLayout>
FrameLayout帧布局
RelativeLayout 相对布局
// 相对于给定ID控件
android:layout_above 将该控件的底部置于给定ID的控件之上;
android:layout_below 将该控件的底部置于给定ID的控件之下;
android:layout_toLeftOf 将该控件的右边缘与给定ID的控件左边缘对齐;
android:layout_toRightOf 将该控件的左边缘与给定ID的控件右边缘对齐;
android:layout_alignBaseline 将该控件的baseline与给定ID的baseline对齐;
android:layout_alignTop 将该控件的顶部边缘与给定ID的顶部边缘对齐;
android:layout_alignBottom 将该控件的底部边缘与给定ID的底部边缘对齐;
android:layout_alignLeft 将该控件的左边缘与给定ID的左边缘对齐;
android:layout_alignRight 将该控件的右边缘与给定ID的右边缘对齐;
// 相对于父组件
android:layout_alignParentTop 如果为true,将该控件的顶部与其父控件的顶部对齐;
android:layout_alignParentBottom 如果为true,将该控件的底部与其父控件的底部对齐;
android:layout_alignParentLeft 如果为true,将该控件的左部与其父控件的左部对齐;
android:layout_alignParentRight 如果为true,将该控件的右部与其父控件的右部对齐;
// 居中
android:layout_centerHorizontal 如果为true,将该控件的置于水平居中;
android:layout_centerVertical 如果为true,将该控件的置于垂直居中;
android:layout_centerInParent 如果为true,将该控件的置于父控件的中央;
// 指定移动像素
android:layout_marginTop 上偏移的值;
android:layout_marginBottom 下偏移的值;
android:layout_marginLeft 左偏移的值;
android:layout_marginRight 右偏移的值;
example:
android:layout_below = "@id/***"
android:layout_alignBaseline = "@id/***"
android:layout_alignParentTop = true
android:layout_marginLeft = “10px”
例子
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:id="@+id/img1"
android:layout_centerInParent="true"
android:src="@drawable/pic1"/>
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:id="@+id/img2"
android:layout_toLeftOf="@id/img1"
android:layout_centerVertical="true"
android:src="@drawable/pic2"/>
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_toRightOf="@id/img1"
android:id="@+id/img3"
android:layout_centerVertical="true"
android:src="@drawable/pic3"/>
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:id="@+id/img4"
android:layout_centerHorizontal="true"
android:layout_above="@id/img1"
android:src="@drawable/pic4"/>
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:id="@+id/img5"
android:layout_centerHorizontal="true"
android:layout_below="@id/img1"
android:src="@drawable/pic5"/>
</RelativeLayout>
网络布局 Gridlayout
自身属性
rowCount 行数
columnCount 列数
内部子组件属性
layout_columnSpan 横向跨几列
layout_rowSpan 纵向上跨几行
layout_column 该子组件在GridLayout的第几列
layout_row 该子组件在GridLayout的第几行
绝对布局 AbsoluteLayout
内部子组件属性
layout_x 该组件的x坐标
layout_y 该组件的y坐标
Android开发约束布局ConstraintLayout学习总结
小伙伴都知道Android开发有常用的五大布局:LinearLayout、RelateLayout、FrameLayout、AbsolutLayout和TableLayout,今天再总结一个比较牛掰的一个布局——ConstraintLayout,完全可以代替LinearLayout和RelateLayout,具体为什么要使用他和怎么用是接下来需要说的。
ConstraintLayout介绍
约束布局ConstraintLayout是一个Support库,可以在Api9以上的Android系统使用它,它的出现主要是为了解决布局嵌套过多的问题,以灵活的方式定位和调整小部件。从Android Studio 2.3起,官方的模板默认使用ConstraintLayout。
ConstraintLayout使用缘由
在开发过程中经常能遇到一些复杂的UI,可能会出现布局嵌套过多的问题,嵌套得越多,设备绘制视图所需的时间和计算功耗也就越多,遇到此情况我们更多的是采用RelativeLayout。
既然用RelativeLayout可以解决问题,为什么还要使用ConstraintLayout呢?因为ConstraintLayout使用起来比RelativeLayout更灵活,性能更出色!还有一点就是ConstraintLayout可以按照比例约束控件位置和尺寸,能够更好地适配屏幕大小不同的机型。
ConstraintLayout用法
添加依赖
首先我们需要在app/build.gradle文件中添加ConstraintLayout的依赖:
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
相对定位
相对定位是部件对于另一个位置的约束,如图:
相应的代码:
<TextView
android:id="@+id/TextView1"
...
android:text="TextView1" />
<TextView
android:id="@+id/TextView2"
...
app:layout_constraintLeft_toRightOf="@+id/TextView1" />
面代码中在TextView2里用到了app:layout_constraintLeft_toRightOf="@+id/TextView1"这个属性,他的意思是把TextView2的左边约束到TextView1的右边
下面8个属性中,constraint后面的Left、Right、Top、Bottom是指View自己的左边还是右边,是上面还是下面;to后面的Left、Right、Top、Bottom是指目标View的左边还是右边,是上面还是下面;
可以这样理解:当前布局的View A的上/下/左/右边是在目标参照物View B的上/下/左/右边
相对定位的常用属性
layout_constraintLeft_toLeftOf
view1左边对齐view2的左边
layout_constraintLeft_toRightOf
view1左边对齐view2的右边
layout_constraintRight_toLeftOf
view1右边对齐view2的左边
layout_constraintRight_toRightOf
view1右边对齐view2的右边
layout_constraintTop_toTopOf
view1顶部对齐view2的顶部
layout_constraintTop_toBottomOf
view1顶部对齐view2的底部
layout_constraintBottom_toTopOf
view1底部对齐view2的顶部
layout_constraintBottom_toBottomOf
view1底部对齐view2的底部
layout_constraintBaseline_toBaselineOf
view1基准线对齐view2的基准线
layout_constraintStart_toEndOf
view1起始位置对齐view2的结束位置
layout_constraintStart_toStartOf
view1起始位置view2的起始位置
layout_constraintEnd_toStartOf
view1结束位置对齐view2的起始位置
layout_constraintEnd_toEndOf
view1结束位置对齐view2的结束位置
两个TextView的高度不一致,但是又希望他们文本对齐,这个时候就可以使用layout_constraintBaseline_toBaselineOf,代码如下:
<TextView
android:id="@+id/TextView1"
.../>
<TextView
android:id="@+id/TextView2"
...
app:layout_constraintLeft_toRightOf="@+id/TextView1"
app:layout_constraintBaseline_toBaselineOf="@+id/TextView1"/>
效果如下:
角度定位
角度定位指的是可以用一个角度和一个距离来约束两个空间的中心。举例:
<TextView
android:id="@+id/TextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/TextView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintCircle="@+id/TextView1"
app:layout_constraintCircleAngle="120"
app:layout_constraintCircleRadius="150dp" />
上面例子中的TextView2用到了3个属性:
app:layout_constraintCircle="@+id/TextView1"
app:layout_constraintCircleAngle=“120”(角度)
app:layout_constraintCircleRadius=“150dp”(距离)
指的是TextView2的中心在TextView1的中心的120度,距离为150dp,效果如下:
边距
常用margin
android:layout_marginStart
android:layout_marginEnd
android:layout_marginLeft
android:layout_marginTop
android:layout_marginRight
android:layout_marginBottom
在使用margin的时候要注意两点:
在ConstraintLayout里面要实现margin,必须先约束该控件在ConstraintLayout里的位置
margin只能大于等于0
goneMargin
goneMargin主要用于约束的控件可见性被设置为gone的时候使用的margin值,属性如下:
layout_goneMarginStart
layout_goneMarginEnd
layout_goneMarginLeft
layout_goneMarginTop
layout_goneMarginRight
layout_goneMarginBottom
假设TextView2的左边约束在TextView1的右边,并给TextView2设一个app:layout_goneMarginLeft=“10dp”,代码如下:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/TextView1"
.../>
<TextView
android:id="@+id/TextView2"
...
app:layout_constraintLeft_toRightOf="@+id/TextView1"
app:layout_goneMarginLeft="10dp"
/>
</android.support.constraint.ConstraintLayout>
效果图:
这个时候把TextView1的可见性设为gone,效果如下:
extView1消失后,TextView2有一个距离左边10dp的边距。
居中
ConstraintLayout居中写法:
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
水平居中:
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
垂直居中:
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
偏移
居中后可采用margin进行适当偏移:
<TextView
android:id="@+id/TextView1"
...
android:layout_marginLeft="100dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
除了这种偏移外,ConstraintLayout还提供了另外一种偏移的属性:
layout_constraintHorizontal_bias 水平偏移
layout_constraintVertical_bias 垂直偏移
例子:
<TextView
android:id="@+id/TextView1"
...
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
解释:
居中时layout_constraintHorizontal_bias默认为0.5即50%(左偏移50%右偏移50%),0.3(左偏移30%右偏移70%)
尺寸约束
控件的尺寸可以通过四种不同方式指定。
使用指定的尺寸
这个就不说了。
使用wrap_content
当控件的高度或宽度为wrap_content时,可以使用下列属性来控制最大、最小的高度或宽度:
android:minWidth 最小的宽度
android:minHeight 最小的高度
android:maxWidth 最大的宽度
android:maxHeight 最大的高度
另外使用这些属性需要加上强制约束:
app:constrainedWidth=”true”
app:constrainedHeight=”true”
使用0dp
官方不推荐在ConstraintLayout中使用match_parent,可以设置 0dp (MATCH_CONSTRAINT) 配合约束代替match_parent,例如:
<TextView
android:id="@+id/TextView1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:visibility="visible" />
宽度设为0dp,左右两边约束parent的左右两边,并设置左边边距为50dp,效果如下:
控件内宽高比
当宽或高至少有一个尺寸被设置为0dp时,可以通过属性layout_constraintDimensionRatio设置宽高比,举个例子:
<TextView
android:id="@+id/TextView1"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
宽设置为0dp,宽高比设置为1:1,这个时候TextView1是一个正方形。
除此之外,在设置宽高比的值的时候,还可以在前面加W或H,分别指定宽度或高度限制。 例如:
app:layout_constraintDimensionRatio="H,2:3"指的是 高:宽=2:3
app:layout_constraintDimensionRatio="W,2:3"指的是 宽:高=2:3
链
如果两个或以上控件通过下图的方式约束在一起,就可以认为是他们是一条链。
例如:
<TextView
android:id="@+id/TextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/TextView2" />
<TextView
android:id="@+id/TextView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/TextView1"
app:layout_constraintRight_toLeftOf="@+id/TextView3"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/TextView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/TextView2"
app:layout_constraintRight_toRightOf="parent" />
个TextView相互约束,两端两个TextView分别与parent约束,成为一条链,效果如下:
链样式
一条链的第一个控件是这条链的链头,我们可以在链头中设置 layout_constraintHorizontal_chainStyle来改变整条链的样式,提供有三种样式:
spread
spread_inside
packed
对应这三种样式的效果图如下:
权重链
可以留意到上面所用到的3个TextView宽度都为wrap_content,如果我们把宽度都设为0dp,这个时候可以在每个TextView中设置横向权重layout_constraintHorizontal_weight(constraintVertical为纵向)来创建一个权重链,如下所示:
<TextView
android:id="@+id/TextView1"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/TextView2"
app:layout_constraintHorizontal_weight="2" />
<TextView
android:id="@+id/TextView2"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/TextView1"
app:layout_constraintRight_toLeftOf="@+id/TextView3"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_weight="3" />
<TextView
android:id="@+id/TextView3"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/TextView2"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_weight="4" />
效果图如下:
辅助工具
Optimizer(约束优化)
当我们使用 MATCH_CONSTRAINT 时,ConstraintLayout 将对控件进行 2 次测量,ConstraintLayout在1.1中可以通过设置 layout_optimizationLevel 进行优化,可设置的值有:
none:无优化
standard:仅优化直接约束和屏障约束(默认)
direct:优化直接约束
barrier:优化屏障约束
chain:优化链约束
dimensions:优化尺寸测量
Barrier(屏障标准)
假设有3个控件ABC,C在AB的右边,但是AB的宽是不固定的,这个时候C无论约束在A的右边或者B的右边都不对。当出现这种情况可以用Barrier来解决。Barrier可以在多个控件的一侧建立一个屏障,如下所示:
这个时候C只要约束在Barrier的右边就可以了,代码如下:
<TextView
android:id="@+id/TextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/TextView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/TextView1" />
<android.support.constraint.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="right"
app:constraint_referenced_ids="TextView1,TextView2" />
<TextView
android:id="@+id/TextView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/barrier" />
其中:
app:barrierDirection为屏障所在的位置,可设置的值有:bottom、end、left、right、start、top
app:constraint_referenced_ids为屏障引用的控件,可设置多个(用“,”隔开)
Group(组概念)
Group可以把多个控件归为一组,方便隐藏或显示一组控件:
<TextView
android:id="@+id/TextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/TextView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/TextView1" />
<TextView
android:id="@+id/TextView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/TextView2" />
现在有3个并排的TextView,用Group把TextView1和TextView3归为一组,再设置这组控件的可见性,如下所示:
Placeholder(占位符)
Placeholder指的是占位符。在Placeholder中可使用setContent()设置另一个控件的id,使这个控件移动到占位符的位置:
<android.support.constraint.Placeholder
android:id="@+id/placeholder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:content="@+id/textview"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#cccccc"
android:padding="16dp"
android:text="TextView"
android:textColor="#000000"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
新建一个Placeholder约束在屏幕的左上角,新建一个TextView约束在屏幕的右上角,在Placeholder中设置 app:content="@+id/textview",这时TextView会跑到屏幕的左上角。效果如下:
Guideline(辅助线)
Guildline像辅助线一样,在预览的时候帮助你完成布局(不会显示在界面上)。
Guildline的主要属性:
android:orientation 垂直vertical,水平horizontal
layout_constraintGuide_begin 开始位置
layout_constraintGuide_end 结束位置
layout_constraintGuide_percent 距离顶部的百分比(orientation = horizontal时则为距离左边)
例如:
<android.support.constraint.Guideline
android:id="@+id/guideline1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="50dp" />
<android.support.constraint.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
guideline1为水平辅助线,开始位置是距离顶部50dp,guideline2位垂直辅助线,开始位置为屏幕宽的0.5(中点位置),效果如下:
guideline效果图
滚动视图ScrollView
由于Android的布局节点都不支持自动滚动,所以出现ScrollView
垂直滚动是ScrollView 水平滚动为 HorizontalScrollView
ScrollView有时内容不够又要让他充满屏幕,使用layout_height_match_parent结果也不充满,正确是添加一行fillViewport属性
android:fillViewport="true"
按钮加图片
Button btn_icon;//带图标的按钮
Drawable drawable;//图形对象
drawable=getResources().getDrawable(R.mipmap.ic_launcher);
btn_icon=findViewById(R.id.btn_icon);
btn_icon.setCompoundDrawable(drawable,null,null,null);//设置文字左边图标
setCompoundDrawable 四个参数分别设置 文字左边 文字上面 文字右边 文字下面 图
如设置文字下面为
btn_icon.setCompoundDrawable(null,null,null,drawable);
Shap使用
也就是资源drawable用xml描述绘制方式
在Android开发中,使用shape可以很方便的帮我们画出想要的背景,相对于png图片来说,使用shape可以减少安装包的大小,而且能够更好的适配不同的手机
android:shap 表示形状
corners 表示四角圆角 bottomLeftRadius bottomRightRadius topLeftRadius topRightRadius
gradient 表示内部的渐变颜色
padding 表示形状与周围的间隙 left bottom right top
size 表示形状的尺寸宽高 无size表示宽高自适应
solid 表示形状内部的填充色,无表示无填充色
stroke 描述图形四周变现的归则定义
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape=["rectangle" | "oval" | "line" | "ring"] >
<corners
android:radius="integer"
android:topLeftRadius="integer"
android:topRightRadius="integer"
android:bottomLeftRadius="integer"
android:bottomRightRadius="integer" />
<gradient
android:angle="integer"
android:centerX="integer"
android:centerY="integer"
android:centerColor="integer"
android:endColor="color"
android:gradientRadius="integer"
android:startColor="color"
android:type=["linear" | "radial" | "sweep"]
android:useLevel=["true" | "false"] />
<padding
android:left="integer"
android:top="integer"
android:right="integer"
android:bottom="integer" />
<size
android:width="integer"
android:height="integer" />
<solid
android:color="color" />
<stroke
android:width="integer"
android:color="color"
android:dashWidth="integer"
android:dashGap="integer" />
</shape>
Intent IntentFiter
是Activity Service BroadcastReceive之间通讯的载体
Activity:
startActivty(Intent intent)
startActivityForResult(Intent intent,int requestCode);
例子
Intent intent(MyClass.this,ToClass.this);
传数据Bundle
Bundle data=new Bundle();
data.putSerializabe("person",P);
intent.putExtras(data);//设置数据
intent.putExtra("data", data);
stratActivity(intent);
在转后的Activity
Intent intent=getIntent();
Person p=(Person)intent.getSerializabeExtra("person");
data=(String) intent.getExtras().get("data");
intent中putExtras和putExtra的区别:
public Intent putExtra (String name, parcelable value)
设置方法 intent.putExtra(“aaa”, “bbbb”);
获取方法 this.getIntent().getCharSequenceExtra(“aaa”)
public Intent putExtras (Bundle extras)
设置方法
Bundle bd = new Bundle();
bd.putString(“aaa”,“bbbb”);
intent.putExtras(bd);
获取方法
Bundle bd=this.getIntent().getExtras();
bd.getString(“aaa”));
总结:带s的得通过个Bundle来绑定数
返回数据
finishActivity(int requestCode);
重写Activity的onActivityResult(...)获得结果
实列:
返回数据
Intent intent=getIntent();
Bundle bd=new Bundle();
bd.putSting("a","12222");
intent.putExtras(bd);
MyClass.this.setResult(0,intent);
MyClass.this.finish();
获得数据
onActivityResult(int requestCode,int resultCode,Intent intent)
{
if(requestCode==0 && resultCod==0)
{
Bundle data=intent.getExtras();
data.getString("a");
}
}
Service:
startService(Intent intent)
bindService(Intent service,ServiceConnection conn, int fag);
BroadcastReceiver:
sendBroadcast(Intent intent),
sendStickyBroadcast(Intent intent)
sendOrderedBroadcast(Intent intent,String receiverPermisson)
ListView的列表数据提供
SimpleAdapter:
例子
List<Map<String<Object>>listitems=new ArrayList<new Map<String,Object>>();
for(int i=0;i<..;i++)
{
Map<String,Object> listItem=new HashMap<String,Object>();
listItem.put("header",imag[i]);
listItem.put("name","xunn");
listItems.Add(listItem);
}
SimpleAdapter simpleAdapter=new SimpleAdapter(this,listItems,new String[]{"personName","header",...},new int[]{R.id.name,R.id....});
ListView listView=(ListView)findViewById(R.id.myList);
//设置数据
listView.setAdapter( simpleAdapter);
//监听单击一个列表项事件
listView.setOnItemClickListener(new OnItemCickListener(){
@override
public void OnItemClick(AdapterView<?>parent,View view,int postion,long id)
{
System.out.println(...);
}
});
//监听选中一个列表项事件
listView.setOnItemSelectedListener(new OnItemCickListener(){
@override
public void OnItemSelected(AdapterView<?>parent,View view,int postion,long id)
{
System.out.println(...);
}
});
复杂的BaseAdapter
使用BaseAdapter比较简单,主要是通过继承此类来实现BaseAdapter的四个方法:
public int getCount(): 适配器中数据集的数据个数;
public Object getItem(int position): 获取数据集中与索引对应的数据项;
public long getItemId(int position): 获取指定行对应的ID;
public View getView(int position,View convertView,ViewGroup parent): 获取没一行Item的显示内容。
下面通过一个简单示例演示如何使用BaseAdapter。
1.创建布局文件
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.cbt.learnbaseadapter.MainActivity">
<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
item.xml (ListView中每条信息的显示布局)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_image"
android:src="@mipmap/ic_launcher"
android:layout_width="60dp"
android:layout_height="60dp"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_toEndOf="@id/iv_image"
android:text="Title"
android:gravity="center"
android:textSize="25sp"/>
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/iv_image"
android:layout_below="@id/tv_title"
android:text="Content"
android:textSize="20sp"/>
</RelativeLayout>
2.创建数据源
ItemBean.java
package com.cbt.learnbaseadapter;
/**
* Created by caobotao on 15/12/20.
*/
public class ItemBean {
public int itemImageResId;//图像资源ID
public String itemTitle;//标题
public String itemContent;//内容
public ItemBean(int itemImageResId, String itemTitle, String itemContent) {
this.itemImageResId = itemImageResId;
this.itemTitle = itemTitle;
this.itemContent = itemContent;
}
}
通过此Bean类,我们就将要显示的数据与ListView的布局内容一一对应了,每个Bean对象对应ListView的一条数据。这种方法在ListView中使用的非常广泛。
MainActivity.java
package com.cbt.learnbaseadapter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
ListView mListView ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<ItemBean> itemBeanList = new ArrayList<>();
for (int i = 0;i < 20; i ++){
itemBeanList.add(new ItemBean(R.mipmap.ic_launcher, "标题" + i, "内容" + i));
}
mListView = (ListView) findViewById(R.id.lv_main);
//设置ListView的数据适配器
mListView.setAdapter(new MyAdapter(this,itemBeanList));
}
}
3.创建BaseAdapter
通过上面的讲解,我们知道继承BaseAdapter需要重新四个方法:getCount、getItem、getItemId、getView。其中前三个都比较简单,而getView稍微比较复杂。通常重写getView有三种方式,这三种方法性能方面有很大的不同。接下来我们使用此三种方式分别实现MyAdapter。
第一种:逗比式
package com.cbt.learnbaseadapter;
import android.content.Context;
import android.view.*;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by caobotao on 15/12/20.
*/
public class MyAdapter extends BaseAdapter{
private List<ItemBean> mList;//数据源
private LayoutInflater mInflater;//布局装载器对象
// 通过构造方法将数据源与数据适配器关联起来
// context:要使用当前的Adapter的界面对象
public MyAdapter(Context context, List<ItemBean> list) {
mList = list;
mInflater = LayoutInflater.from(context);
}
@Override
//ListView需要显示的数据数量
public int getCount() {
return mList.size();
}
@Override
//指定的索引对应的数据项
public Object getItem(int position) {
return mList.get(position);
}
@Override
//指定的索引对应的数据项ID
public long getItemId(int position) {
return position;
}
@Override
//返回每一项的显示内容
public View getView(int position, View convertView, ViewGroup parent) {
//将布局文件转化为View对象
View view = mInflater.inflate(R.layout.item,null);
/**
* 找到item布局文件中对应的控件
*/
ImageView imageView = (ImageView) view.findViewById(R.id.iv_image);
TextView titleTextView = (TextView) view.findViewById(R.id.tv_title);
TextView contentTextView = (TextView) view.findViewById(R.id.tv_content);
//获取相应索引的ItemBean对象
ItemBean bean = mList.get(position);
/**
* 设置控件的对应属性值
*/
imageView.setImageResource(bean.itemImageResId);
titleTextView.setText(bean.itemTitle);
contentTextView.setText(bean.itemContent);
return view;
}
}
为什么称这种getView的方式是逗比式呢?
通过上面讲解,我们知道ListView、GridView等数据展示控件有缓存机制,而这种方式每次调用getView时都是通过inflate创建一个新的View对象,然后在此view中通过findViewById找到对应的控件,完全没有利用到ListView的缓存机制。这种方式没有经过优化处理,对资源造成了极大的浪费,效率是很低的。
第二种:普通式
public View getView(int position, View convertView, ViewGroup parent) {//如果view未被实例化过,缓存池中没有对应的缓存
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item,null);
}
/**
* 找到item布局文件中对应的控件
*/
ImageView imageView = (ImageView) convertView.findViewById(R.id.iv_image);
TextView titleTextView = (TextView) convertView.findViewById(R.id.tv_title);
TextView contentTextView = (TextView) convertView.findViewById(R.id.tv_content);
//获取相应索引的ItemBean对象
ItemBean bean = mList.get(position);
/**
* 设置控件的对应属性值
*/
imageView.setImageResource(bean.itemImageResId);
titleTextView.setText(bean.itemTitle);
contentTextView.setText(bean.itemContent);
return convertView;
}
此方式充分使用了ListView的缓存机制,如果view没有缓存才创建新的view,效率相比于逗比式提升了很多。但是,当ListView很复杂时,每次调用findViewById都会去遍历视图树,所以findViewById是很消耗时间的,我们应该尽量避免使用findViewById来达到进一步优化的目的。
第三种:文艺式
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
//如果view未被实例化过,缓存池中没有对应的缓存
if (convertView == null) {
viewHolder = new ViewHolder();
// 由于我们只需要将XML转化为View,并不涉及到具体的布局,所以第二个参数通常设置为null
convertView = mInflater.inflate(R.layout.item, null);
//对viewHolder的属性进行赋值
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.iv_image);
viewHolder.title = (TextView) convertView.findViewById(R.id.tv_title);
viewHolder.content = (TextView) convertView.findViewById(R.id.tv_content);
//通过setTag将convertView与viewHolder关联
convertView.setTag(viewHolder);
}else{//如果缓存池中有对应的view缓存,则直接通过getTag取出viewHolder
viewHolder = (ViewHolder) convertView.getTag();
}
// 取出bean对象
ItemBean bean = mList.get(position);
// 设置控件的数据
viewHolder.imageView.setImageResource(bean.itemImageResId);
viewHolder.title.setText(bean.itemTitle);
viewHolder.content.setText(bean.itemContent);
return convertView;
}
// ViewHolder用于缓存控件,三个属性分别对应item布局文件的三个控件
class ViewHolder{
public ImageView imageView;
public TextView title;
public TextView content;
}
此方式不仅利用了ListView的缓存机制,而且使用ViewHolder类来实现显示数据视图的缓存,避免多次调用findViewById来寻找控件,以达到优化程序的目的。所以,大家在平时的开发中应当尽量使用这种方式进行getView的实现。
总结一下用ViewHolder优化BaseAdapter的整体步骤:
>1 创建bean对象,用于封装数据;
>2 在构造方法中初始化的数据List;
>3 创建ViewHolder类,创建布局映射关系;
>4 判断convertView,为空则创建,并设置tag,不为空则通过tag取出ViewHolder;
>5 给ViewHolder的控件设置数据。
刷新ListView项
public void updataView(int posi, ListView listView) {
int visibleFirstPosi = listView.getFirstVisiblePosition();
int visibleLastPosi = listView.getLastVisiblePosition();
if (posi >= visibleFirstPosi && posi <= visibleLastPosi) {
View view = listView.getChildAt(posi - visibleFirstPosi);
ViewHolder holder = (ViewHolder) view.getTag();
String txt = holder.strText.getText().toString();
txt = txt + "++;";
holder.strText.setText(txt);
strList.set(posi, txt);
} else {
String txt = strList.get(posi);
txt = txt + "++;";
strList.set(posi, txt);
}
}
安卓中删除list中的某一项,并不是删除了一个view,而是删除了adapter中的数据源的list里面的一项,然后adapter.notifydatasetchanged()通知list去刷新界面,这时候就会删除某一项。
而RecycleView这个列表支持单独删除一项,并伴有动画,调用adapter.notifyRemove(position)即可。
AlertDialog.Builder
setView
添加按钮
setPositiveButton
setNagativeButton
setNeutralButton
view root=this.getLayoutInflater().inflate(R.layout.win);
Button bt=(Button)root.findViewById(R.id.bt);
View 内部事件
1.基于事件监听方式
View.OnClickListener 点击事件
View.OnCreateContextMenuListener 创建上下文菜单事件
View.OnFocusChangeListener 焦点改变事件
View.OnKeyListener 按键事件
View.OnLongClickListener 长按事件
View.OnTouchListener 触摸事件
.setOnClickListener(new OnClickListener(){
@override
public ....
}
使用外部类监听事件可以附加信息到新类
如:
public class smsSendListener implements onLongClickListener{
private EditText address;
pubic smsSendListener(Activity act,EditText address)
{
}
..
@override
public boolean onLongClick(View source)
{
.....
}
}
使用
view.setOnLongClickListener(new smsSendListener(this,m_Address));
Activity本身作为事件监听类只需要implements相应的接口实现接口函数
view.setOnLongClickListener(this);
推荐这种方式通过获得view id来区分是那个组件的事件
view.getId();
view附加数据
view.setTag(..);
view.getTagf();
事件绑定到标签
android:onClick="clickHandler"
pubic void clickHandler(View source)
{
}
2.基于事件回调机制
假设说事件监听机制是一种托付式的事件处理,那么回调机制则与之相反,对于基于回调的事件处理模型来说,事件源和事件监听器是统一的,或者说事件监听器全然消失了,当用户在GUI控件上激发某个事件时,控件自己特定的方法将会负责处理该事件。cut
常用的回调事件
onKeyDown
onKeyLongPress
onKeyShortcut
onKeyUp
onTouchEvent
onTrackballEvent
一、View类的常见回调方法
为了使用回调机制来处理GUI控件上所发生的事件,须要为该组件提供相应的事件处理方法,而Java又是一种静态语言,我们无法为每一个对象动态地加入方法。因此仅仅能通过继承GUI控件类,并重写该类的事件处理方法来实现。 Android平台中。每一个View都有自己处理特定事件的回调方法,我们能够通过重写View中的这些回调方法来实现对应的事件。
二、基于回调的事件处理开发方法
1.自己定义控件的一般步骤
(1)定义自己组件的类名。并让该类继承View类或一个现有的View的子类;
(2)重写父类的一些方法,通常须要提供一个构造器,构造器是创建自己定义控件的基本方式。
当Java代码创建该控件或依据XML布局文件载入并构建界面时都将调用该构造器,依据业务须要重写父类的部分方法。比如onDraw方法,用于实现界面显示,其它方法还有onSizeChanged()、onKeyDown()、onKeyUp()等。
(3)使用自己定义的组件,既可通过Java代码来创建,也能够通过XML布局文件进行创建,须要注意的是在XML布局文件里,该组件的标签是完整的包名+类名,而不再不过原来的类名。
2.源代码实战
实现:自己定义一个button。重写其触摸的回调方法。
(1)MyButton.java:自己定义组件
package com.example.android_event2;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.Button;
import android.widget.Toast;
public class MyButton extends Button
{
//构造方法
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
//回调方法
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Toast.makeText(getContext(),"您按下了数字:"+keyCode,Toast.LENGTH_SHORT).show();
return true; //返回true,表明该事件不会向外扩散
}
}
(2)layouyt/main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!--方法一:在XML布局文件里创建组件-->
<com.example.android_event2.MyButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="回调測试" />
</LinearLayout>
(3)MainActivity.java:主Activity
package com.example.android_event2;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//方法二:Java代码中配置自己定义组件
}
在一个按钮响应用户的动作时,有一定的顺序,而且, 同事绑定view和button的点击事件,button子类的点击事件会覆盖父类view的响应事件。
package xueyou.xueyoucto.com.androidviews;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private final String Tag = "MAINACTIVITY";
public View view;
public Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
view = this.findViewById(R.id.mainRelativeLayout);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "click me", Toast.LENGTH_SHORT).show();
}
});
button = (Button)this.findViewById(R.id.button);
//这样是先执行OnTouch再执行OnClick事件
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "按钮被单击", Toast.LENGTH_SHORT).show();
}
});
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Toast.makeText(MainActivity.this, "按钮被触摸", Toast.LENGTH_SHORT).show();
return false;
}
});
}
}
运行结果:
点击按钮是,先弹出OnTouch事件,在进行Onclick事件,不会响应view的onclick事件。
单独点击view 的时候,会响应view的onClick事件。
响应系统设置事件
Configuration cfg=getResources().getConfiguration();
重写Activity的onConfigurationChanged(Configuration newConfig);
监听系统配置发生变化
要使用该回调需要再配置Activity时候指定android:configChanges属性 支持 mmc mnc screenSize orientation 等
非主线程使用Handler,接收消息做处理
每个线程有个Looper
Class MyThread extends Thread
{
public void run()
{
Looper.prpare();
myHander=new Hander(){
@override
pubic void handleMessage(Message msg)
{
....
}
Looper,Loop();
}
}
}
异步任务
AsyncTask <Params, Progress, Result>
Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型。
Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型。
Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型。
我们在定义一个类继承 AsyncTask 类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成 void。
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
使用时只需要集成 AsyncTask,创建对象并调用 execute 执行即可:
new DownloadFilesTask().execute(url1, url2, url3);
doInBackground(Params…) 方法里执行耗时逻辑,然后在 onPostExecute(Result) 中将结果更新回UI组件
AsyncTask 的几个主要方法中,doInBackground 方法运行在子线程,execute、onPreExecute、onProgressUpdate、onPostExecute 这几个方法都是在 UI 线程运行的。
使用 AsyncTask 的注意事项
AsyncTask 的实例必须在 UI Thread 中创建。
只能在 UI 线程中调用 AsyncTask 的 execute 方法。
AsyncTask 被重写的四个方法是系统自动调用的,不应手动调用。
每个 AsyncTask 只能被执行一次,多次执行会引发异常。
AsyncTask 的四个方法,只有 doInBackground 方法是运行在其他线程中,其他三个方法都运行在 UI 线程中,也就说其他三个方法都可以进行 UI 的更新操作。
AsyncTask 默认是串行执行,如果需要并行执行,使用接口 executeOnExecutor 方法。
优点:
结构清晰,使用简单,适合后台任务的交互。
异步线程的优先级已经被默认设置成了:THREAD_PRIORITY_BACKGROUND,不会与 UI 线程抢占资源。
缺点:
结构略复杂,代码较多。
每个 AsyncTask 只能被执行一次,多次调用会发生异常。
AsyncTask 在整个 Android 系统中维护一个线程池,有可能被其他进程的任务抢占而降低效率。
Android 中,实现异步任务的六种方式:
使用 Thread 创建
使用 Thread + Looper + handler
使用 AsyncTask
使用 HandlerThread
使用 IntentService
使用线程池来处理异步任务
// 主线程更新数据
runOnUiThread(new Runnable() {
@Override
public void run() {
String result = "每隔1秒更新一下数据:"+Math.random();
mInfoText.setText(result);
}
});
线程简写
new Thread(() -> {
//do something takes long time in the work-thread
runOnUiThread(() -> {
textView.setText("test");
});
}).start();
Activity Service ContentProvider BroadcastReceive 应用组件都必须显示配置
Acttivity加载模式
standard 标准模式 每次创建新的
singleTop:Task顶单例模式 位于顶部则不创建否则创建
singleTask:Task内单例模式 不在栈顶那么移除所有Activity将自己置为顶部
singleInstance:全局单例模式 只会创建一个 Activity,并使用一个全新的Task装载该Activity
启动一个应用时,Android就会为只创建一个 Task,然后启动这个应用入口Activity
Activity可以调用getTaskId()获得所在的Task
Task可以理解为Activity栈
Fragment
fragment通过getActivity()获得所在的Activity,Activity可以调用FragmentManager的findFragmentByid或findFragmentBytag方法获得Fragment
Activity运行可以调用FragmentManager的add remove replace方法动态地添加删除或代替Fragment
一个Activity可以拥有多个Fragment,一个 Fragment也可以被多个Activity复用
Fragment可以响应自己的输入事件,并拥有自己的生命周期
Fragment和Activity重载的方法类似
onCreate 创建
onCreateView 创建view
View rootView=inflater.inflat(R.layout.fragment,container,false);
rootView.findViewById(R.id.A);
...
return rootView;
添加fragment的方法
1.在布局文件里添加
2.在Activity中通过FragmentManager 添加
Intent配置
action category data type
Intent intent=new Intent();
intent.setAction(ActionAtttr.CREATE_ACTION);//参数为串
intent.startActivity(intent);
<intent-filter>
0-N个<action android:name="hhhhh.A"> setAction
0-N个<category android:name="ddddd"> addCategory(String str);
0-N个<data android:name="ddddddddd">
指定Action Category 调用系统Activity
系统内置Action
android.intent.action.MIAN
android.intent.action.CALL 直接向指定 用户打电话
.....
内置Category
android.intent.category.DEFAULT 默认Category
android.intent.category.HOME 设置该Activity随系统启动而运行
。。。
data 向Action提供操作的数据,Data属性接受一个Uril对象 url字符总满足 scheme://host:port/path
如:
<data android:scheme="lee"/>
<data android:host="www.jd.com"/>
<data android:port="8888"/>
<data android:path="test"/>
<data android:mimeType="abc/xyz">
调用:
Intent intent=new Intent();
intent.setType("abc/xyz");
intent.setData(Uel.parse("lee://www.jd.com:8888/test"));
startActivity(intent);
资源使用
Resource res=getResources();
String mainTitle=res.getText(R.string.main_title);
Drawable logo=res.getDrawable(R.drawable.logo);
int[]arr=res.getIntArray(R.array.books);
布局文件使用@string/myname @+id/myid @dimen/title_font_size @color/red
Bitmap BitmapDrawable
BitmapDrawable drawable=new BitmapDrawable(bitmap);
Bitmap bitmap=drawabe.getBitmap();
bitmap可以通过多种方式获得
createBitmap
也可以通过BitmapFactory来构造
decodeFile 等
ImageView img;
img.setImageBitmap(Bitmapfactory.decodeFile(..."));
绘图 Canvas Paint等
Paint paint=new Paint();
paint.setColor(Color.BLUE);
canvas.DrawCircle(40,40,30,point);
图像混合模式
模式 组合算法 效果
ADD Saturate(S + D) 饱和相加,对图像饱和度进行相加
CLEAR [0, 0] 交集区域alpha和rgb值均为0
DARKEN [Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] 变暗,较深的颜色会覆盖浅色
DST [Da, Dc] 交集区域只显示DST的透明度和色值
DST_ATOP [Sa, Sa * Dc + Sc * (1 - Da)] 相交处绘制DST的部分,其他区域只显示SRC
DST_IN [Sa * Da, Sa * Dc] 只在DST和SRC相交的地方绘制DST的部分
DST_OUT [Da * (1 - Sa), Dc * (1 - Sa)] 只在DST和SRC交集之外的区域绘制DST的部分
DST_OVER [Sa + (1 - Sa)Da, Rc = Dc + (1 - Da)Sc] DST盖在SRC上面
LIGHTEN [Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] 相交的区域变亮
MULTIPLY [Sa * Da, Sc * Dc] 透明度和色值均不为0的地方绘制
OVERLAY 叠加效果
SCREEN [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] 保留两个图层中较白的部分,较暗的部分被遮盖
SRC [Sa, Sc] 交集区域只显示SRC的透明度和色值
SRC_ATOP [Da, Sc * Da + (1 - Sa) * Dc] 相交处绘制SRC的部分,其他区域只显示DST
SRC_IN [Sa * Da, Sc * Da] 只在DST和SRC相交的地方绘制SRC的部分
SRC_OUT [Sa * (1 - Da), Sc * (1 - Da)] 只在DST和SRC交集之外的区域绘制SRC的部分
SRC_OVER [Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc] SRC盖在DST上面
XOR [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] 相交的区域受透明度和色值的影响,如果完全不透明相交处不绘制
它们都是根据以上公式计算出来对应的绘制形式,其中,Sa、Sc代表源图像的透明度(Src Alpha)和色值(Src Color),Ds、Dc代表目标图像的透明度(Dst Alpha)和色值(Dst Color),例如 DST_IN 模式,它是根据 [Sa * Da, Sa * Dc] 算法来进行绘制,按照上文讲的 [Alpha,RGB] 的公式套进去,那么就是:
交集区域的透明度 = 源图像的透明度 * 目标图像的透明度
交集区域的色值 = 源图像的透明度 * 目标图像的色值
可以看到,无论是透明度还是色值,源图像只有透明度派上用场,换句话说,就是去除了源图像的色值,那么就只剩透明度了,所以在源图像和目标图像的交集区域,只会显示出目标图像的效果,但是其透明度会受到源图像的透明度的影响
如何使用混合模式?
1.禁用硬件加速
Android中混合模式的操作其实不复杂,首先,也是容易遗漏的一个步骤,就是禁用硬件加速,部分混合模式的使用必须在禁用硬件加速的前提下进行,否则出来的效果会所出入,因此一般保险起见,使用混合模式之前都禁用硬件加速,如果不想影响应用的其他地方,可以只在自定义View的构造方法中调用:
//禁用硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
2.绘制目标图像
先用画笔绘制一个形状或图片,作为DST图:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(dstBm, 0, 0, paint);
}
3.设置混合模式
接着为画笔设置一个混合模式:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
4.绘制源图像
canvas.drawBitmap(srcBm, 0, 0, paint);
5.清除混合模式
最后清除画笔混合模式,以防止下次调用onDraw的时候受影响:
paint.setXfermode(null);
汇总起来也就是:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(dstBm, 0, 0, paint);
PorterDuffXfermode duffXfermode = new PorterDuffXfermode(PorterDuff.Mode.ADD);
paint.setXfermode(duffXfermode);
canvas.drawBitmap(srcBm, 0, 0, paint);
paint.setXfermode(null);
}
颜色矩阵运算
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 生成色彩矩阵
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
0.5F, 0, 0, 0, 0,
0, 0.5F, 0, 0, 0,
0, 0, 0.5F, 0, 0,
0, 0, 0, 1, 0, 0,
});
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
// 设置画笔颜色为自定义颜色
mPaint.setColor(Color.argb(255, 255, 128, 103));
// 绘制圆环 (x坐标,y坐标,半径,画笔)
canvas.drawCircle(240, 600 / 2, 200, mPaint);
}
我们来看看这个矩阵是怎么计算的。其实说白了就是矩阵之间的运算乘积:
矩阵ColorMatrix的一行乘以矩阵MyColor的一列作为矩阵Result的一行,这里MyColor的RGBA值我们需要转换为[0, 1]。通过这种计算我们可以将我们自己设定的颜色和矩阵的颜色进行叠加,创立一个新的颜色。
双缓存实现动画
Bitmap cacheBitmap;
Canvas cacheCanvas;
Paint paint;
cacheBitmap=CreateBitmap(VIEW_WIDTH,VIEW_HEIGHT,Config.ARGB_8888);
cacheCanvas=new Canvas();
cacheCanvas.setBitmap(cacheBitmap);
paint=new Paint();
paint.setColor(Color.RED);
....
cacheCanvas.drawPath(path,point);
共享数据SharedPreferences
轻量级存储工具
SharedPreferences shared=getSharedPreferences("share",MODE_PRIVATE);
SharedPreferences.Editor editor=shared.edit();
editor.putString("name","MM");
....
editor.commit();
//获得数据
shared.getString("MM","");
SQLite存储
SQLiteDatabase db=openOrCreateDatabase(getFilesDir()+"/test.db",Context.MODE_PRIVATE,null);
delteDatabase(getFilesDir()+"/test.db");
管理类
openDatabase isOpen close getVersion setVersion
事务类
beginTransation setTransactionSuccessful endTransaction
数据处理类
execSQL delete update insert query rawQuery
创建表
private void createTable(SQLiteDatabase db){
//创建表SQL语句
String stu_table="create table usertable(_id integer primary key autoincrement,sname text,snumber text)";
//执行SQL语句
db.execSQL(stu_table);
}
插入数据
插入数据有两种方法:
①SQLiteDatabase的insert(String table,String nullColumnHack,ContentValues values)方法,
参数1 表名称,
参数2 空列的默认值
参数3 ContentValues类型的一个封装了列名称和列值的Map;
②编写插入数据的SQL语句,直接调用SQLiteDatabase的execSQL()方法来执行
private void insert(SQLiteDatabase db){
//实例化常量值
ContentValues cValue = new ContentValues();
//添加用户名
cValue.put("sname","xiaoming");
//添加密码
cValue.put("snumber","01005");
//调用insert()方法插入数据
db.insert("stu_table",null,cValue);
}
private void insert(SQLiteDatabase db){
//插入数据SQL语句
String stu_sql="insert into stu_table(sname,snumber) values('xiaoming','01005')";
//执行SQL语句
db.execSQL(sql);
}
删除数据
删除数据也有两种方法:
①调用SQLiteDatabase的delete(String table,String whereClause,String[] whereArgs)方法
参数1 表名称
参数2 删除条件
参数3 删除条件值数组
②编写删除SQL语句,调用SQLiteDatabase的execSQL()方法来执行删除。
第一种方法的代码:
private void delete(SQLiteDatabase db) {
//删除条件
String whereClause = "id=?";
//删除条件参数
String[] whereArgs = {String.valueOf(2)};
//执行删除
db.delete("stu_table",whereClause,whereArgs);
}
第二种方法的代码:
private void delete(SQLiteDatabase db) {
//删除SQL语句
String sql = "delete from stu_table where _id = 6";
//执行SQL语句
db.execSQL(sql);
}
修改数据
修改数据有两种方法:
①调用SQLiteDatabase的update(String table,ContentValues values,String whereClause, String[] whereArgs)方法
参数1 表名称
参数2 跟行列ContentValues类型的键值对Key-Value
参数3 更新条件(where字句)
参数4 更新条件数组
②编写更新的SQL语句,调用SQLiteDatabase的execSQL执行更新。
第一种方法的代码:
private void update(SQLiteDatabase db) {
//实例化内容值 ContentValues values = new ContentValues();
//在values中添加内容
values.put("snumber","101003");
//修改条件
String whereClause = "id=?";
//修改添加参数
String[] whereArgs={String.valuesOf(1)};
//修改
db.update("usertable",values,whereClause,whereArgs);
}
第二种方法的代码:
private void update(SQLiteDatabase db){
//修改SQL语句
String sql = "update stu_table set snumber = 654321 where id = 1";
//执行SQL
db.execSQL(sql);
查询数据
在Android中查询数据是通过Cursor类来实现的,当我们使用SQLiteDatabase.query()方法时,会得到一个Cursor对象,Cursor指向的就是每一条数据。它提供了很多有关查询的方法,具体方法如下:
public Cursor query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy,String limit);
各个参数的意义说明:
参数table:表名称
参数columns:列名称数组
参数selection:条件字句,相当于where
参数selectionArgs:条件字句,参数数组
参数groupBy:分组列
参数having:分组条件
参数orderBy:排序列
参数limit:分页查询限制
参数Cursor:返回值,相当于结果集ResultSet
Cursor是一个游标接口,提供了遍历查询结果的方法,如移动指针方法move(),获得列值方法getString()等.
Cursor游标常用方法:
方法名称
方法描述
getCount()
获得总的数据项数
isFirst()
判断是否第一条记录
isLast()
private void query(SQLiteDatabase db) {
//查询获得游标
Cursor cursor = db.query ("usertable",null,null,null,null,null,null);
//判断游标是否为空
if(cursor.moveToFirst() {
//遍历游标
for(int i=0;i<cursor.getCount();i++){
cursor.move(i);
//获得ID
int id = cursor.getInt(0);
//获得用户名
String username=cursor.getString(1);
//获得密码
String password=cursor.getString(2);
//输出用户信息 System.out.println(id+":"+sname+":"+snumber);
}
}
}
删除指定表
编写插入数据的SQL语句,直接调用SQLiteDatabase的execSQL()方法来执行
private void drop(SQLiteDatabase db){
//删除表的SQL语句
String sql ="DROP TABLE stu_table";
//执行SQL
db.execSQL(sql);
}
派生Application
重载函数
onCreate 在App启动时候
onTerminate 在App退出时调用
onLowmemory 在低内存时调用
onConfigurationChanged 在配置发生变化的时候调动
利用Application操作全局变量
public class MainApplication extends Application{
private static MainApplication mApp;
//获得当前Application
public static MainApplication getInstance()
{
return mApp;
}
@overrid
public void onCreate()
{
super.onCreate();
mApp=this;
}
}
在Activity中可以调用 MainApplication.getInstance获得实例
Service
通常是"默默"的为我们服务的。为什么说是默默,因为它并不像Activity一样,能够被我们看到。通常,它用于在后台为我们执行一些耗时,或者需要长时间执行的一些操作的。下面让我们来看看它的基本用法
Service的创建
1.任何一个对象,想要发挥其作用,那么就应该首先创建出来。Service的创建和Activity类似,也是通过Intent来实现的。而且既然是安卓四大组件之一,那么它也需要在清单文件中进行注册的。下面,看一下一个简单的创建Service的例子:
public class SimpleService extends Service {
public static final String TAG = "SimpleService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
2.首先,创建一个类SimpleService继承自Service,然后重写它的onCreate,onStartCommand,onDestroy方法,并分别在它们的方法体中打入Log日志。其中onBind方法是默认实现的,具体作用后面会讲到。然后呢,千万不要忘记要在清单文件中注册它,其实如果你是通过Android Studio直接new了一个Service的话,Android Studio会默认帮助你在清单文件中添加对该Service的注册,代码如下:
<service android:name=".ui.main.SimpleService"
android:enabled="true"
android:exported="true"/>
我直接在我之前做的一个项目中新建的,所以不要在意包路径的命名,就是包名点类名。细心的朋友可能看到了下面还有两个属性。其中enabled属性,是指该服务是否能够被实例化。如果设置为true,则能够被实例化,否则不能被实例化,默认值是true。一般情况下,我们都会需要实例化,所以也可以选择不设置。而exported属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true,则能够被调用或交互(通常如果一个服务需要跨进程使用需要这么设置),否则不能。设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。
3.接下来创建一个StartActivity,用于在其中创建SimpleService对象。代码如下:
public class StartActivity extends AppCompatActivity implements View.OnClickListener {
private Button startBtn, stopBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start);
startBtn = (Button) findViewById(R.id.btn_start_service);
stopBtn = (Button) findViewById(R.id.btn_stop_service);
startBtn.setOnClickListener(this);
stopBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v != null) {
switch (v.getId()) {
case R.id.btn_start_service:
Intent startIntent = new Intent(this, SimpleService.class);
startService(startIntent);
break;
case R.id.btn_stop_service:
Intent stopIntent = new Intent(this, SimpleService.class);
stopService(stopIntent);
break;
}
}
}
}
当一个Service被创建以后,再次调用startService方法,Service是不会被重新创建的,而是会重新执行onStartCommand方法。无论我们点击多少次start按钮,始终只会执行onStartCommand方法
可以看到,Service执行了onDestroy方法,这时服务就已经停止了。
以上就是简单的创建一个服务的流程。然而,在我们日常开发中,我们经常需要在服务中做一些逻辑操作,然后将结果返回给一个Activity,即要实现Service和Activity的通信,接下来,让我们看看如何让二者建立起联系
Service与Activity之间的通信
现在我们修改一下前面的代码,SimpleService代码修改如下:
public class SimpleService extends Service {
public static final String TAG = "SimpleService";
private SimpleBinder mBinder;
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
mBinder = new SimpleBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
public IBinder onBind(Intent intent) {
if (mBinder != null) {
return mBinder;
}
return null;
}
class SimpleBinder extends Binder {
public void doTask() {
Log.d(TAG, "doTask");
}
}
}
现在,我们在SimpleService中创建了一个SimpleBinder类,继承自Binder。然后,在里面创建了一个doTask方法,模拟执行一个任务
然后,我们再在StartActivity中加入两个按钮,分别用于绑定服务和解绑服务
public class StartActivity extends AppCompatActivity implements View.OnClickListener {
public static final String TAG = "SimpleService";
private Button startBtn, stopBtn, bindBtn, unBindBtn;
private SimpleService.SimpleBinder mBinder;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, name.toString());
mBinder = (SimpleService.SimpleBinder) service;
mBinder.doTask();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, name.toString());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start);
startBtn = (Button) findViewById(R.id.btn_start_service);
stopBtn = (Button) findViewById(R.id.btn_stop_service);
bindBtn = (Button) findViewById(R.id.btn_bind_service);
unBindBtn = (Button) findViewById(R.id.btn_un_bind_service);
startBtn.setOnClickListener(this);
stopBtn.setOnClickListener(this);
bindBtn.setOnClickListener(this);
unBindBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v != null) {
switch (v.getId()) {
case R.id.btn_start_service:
Intent startIntent = new Intent(this, SimpleService.class);
startService(startIntent);
break;
case R.id.btn_stop_service:
Intent stopIntent = new Intent(this, SimpleService.class);
stopService(stopIntent);
break;
case R.id.btn_bind_service:
Intent bindIntent = new Intent(this, SimpleService.class);
bindService(bindIntent, mConnection, BIND_AUTO_CREATE);
break;
case R.id.btn_un_bind_service:
unbindService(mConnection);
break;
}
}
}
}
我们创建了一个ServiceConnection的匿名内部类,并实现了onServiceConnected和onServiceDisconnected两个方法。ServiceConnection可以看做是一个由Activity操作的代表,负责与Service进行连接,当Activity与Service连接成功时,会执行onServiceConnected方法,相反的,当二者断开连接的时候,会执行onServiceDisconnected方法。当二者连接成功时,在onServiceConnected方法中,我们可以获取到SimpleService中的SimpleBinder的实例对象,然后我们就可以调用其所有的公共方法来实现我们想要做的事了。
Service的执行线程
很多对Service了解的不是很透彻的同学,当被问到Service运行在什么线程中,很多人都会第一反应是子线程。原因呢,因为大多数人都知道Service通常用来执行一些比较耗时的后台任务,既然提到了耗时,那么肯定不会运行在主线程啊,因为那样的话会阻塞线程的啊。其实呢,并不是这样的,Service其实是运行在主线程的
Service确实运行在主线程,但是我们如果执行耗时操作,我们可以在Service里面开启一个子线程来执行耗时任务啊。那么问题又来了,我们在Activity中就可以创建子线程何必还费二遍事开始一个服务来创建一个子线程呢。这里就不得不说出Service的一个特性了,那就是不管创建Service的Activity是否被摧毁,或者说即使退出了应用程序,但只要应用的进程没有被杀死,这个Service就还会在运行着。而对于Activity来说,一旦Activity被摧毁,那么在它里面创建的线程也就不存在,这样一来执行的任务也就停止了。而如果放在Service中执行,即便是关联的Activity被摧毁了,那么只要重新与Service进行关联,那么它还是会获得原有Service中的Binder实例的。举出这样一个需求,假设应用需要在进程未被杀死的情况下时刻保持着从服务器读取一个状态,很明显这时候就可以用Service来实现了啊
做耗时操作的Service例子
public class SimpleService extends Service {
private SimpleBinder mBinder;
@Override
public void onCreate() {
super.onCreate();
mBinder = new SimpleBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 任务逻辑
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
if (mBinder != null) {
return mBinder;
}
return null;
}
class SimpleBinder extends Binder {
public void doTask() {
new Thread(new Runnable() {
@Override
public void run() {
// 任务逻辑
}
}).start();
}
}
}
Broadcast Receive
广播接收(Broadcast Receive)为android的四大组件之一。主要用于监听广播消息,并做出响应。与应用程序中监听事件相比而言,该监听事件为全局监听。
adcast Receive分为两个部分,一个用于发送广播,一个用于接收广播。而发送广播的方式有两种,一种为发送普通广播,一种为发送有序广播。
广播使用步骤:
1. 在intent中写入发送的action。
2. 发送者通过sendBroadcast(intent)和sendOrderedBroadcast(intent, receiverPermission)两种方式进行发送。
3. 接受者继承BroadcastReceiver,并在onReceive方法中接收广播。
广播发送形式:
普通广播:
1. 该发送方式为异步发送,接受者几乎同一时刻接收。
2. 若无找到该广播接收者,应用不会发生异常。
3. 可通过intent进行信息传递。
4. 接收者未启动状态下,可接收广播,并在后台中启动该应用。
Intent intent = new Intent();
intent.setAction("com.mzzhang.broadcastsend.Normal");
intent.putExtra("message", "this is a normal broadcast");
sendBroadcast(intent);
通过定义Intent意图,并添加Action,最后通过intent进行信息传递。
而广播接收者继承broadcastReceiver会在onReceive中接收到广播发送的信息。
有序广播:
1. 通过接收者在AndroidManifest.xml中设置接收优先级Priority或在代码中动态设置setPriority。优先级范围-1000到1000,数值越大,优先级越高。
2. 可在发送广播中将广播进行中断,而后面的接收者将无法接收到广播通知。
3. 也可在优先级高的广播中添加信息,并传递给下个接收者。
4. 接收者在未启动广播情况下,会自动接收广播,并在后台启动该应用。
Intent intent = new Intent();
intent.setAction("com.mzzhang.broadcastsend.ordered");
intent.putExtra("message", "this is a ordered broadcast");
sendOrderedBroadcast(intent, null);
该启动方式与普通广播方式一致。区别在于sendOrderedBroadcast。
广播接收:
1. 广播接收继承BroadcaseReceive,并在onReceive中接收该广播。
2. onReceive为主线程,若在onReceive中响应时间过长会照常ANR。
3. 若需要长时间操作可通过广播发送一个命令启动Servie。
4. 监听方式可分为动态监听和静态接听方式。
静态监听注册方式:
复制代码
<receiver android:name="com.mzzhang.broadcastsend.ReceiveNormal">
<intent-filter android:priority="50">
<action android:name="com.mzzhang.broadcastsend.Normal"/>
<action android:name="com.mzzhang.broadcastsend.ordered"/>
</intent-filter>
</receiver>
复制代码
其中android:priority为优先级时使用
动态监听注册方式:
IntentFilter filter=new IntentFilter("com.mzzhang.broadcastsend.ordered");
filter.setPriority(100);
ReceiveTwo receiver = new ReceiveTwo();
registerReceiver(receiver, filter);
在onReceive接收,并添加信息。
Bundle extras = new Bundle();
extras.putString("message", message.toString());
setResultExtras(extras);
在下个优先级中接收,并获取上个结果信息。
Bundle bundle = getResultExtras(true);
结束往下传递:abortBroadcast();
@Override
public void onReceive(Context context, Intent intent) {
//第三部,接收广播
String name = intent.getExtras().getString("name");
Toast.makeText(context,"动态注册广播后,接收到的名字是"+name,Toast.LENGTH_SHORT).show();
}
NotificationManager详解
1.获取NotificationManager的实例
调用Context的getSystemService()方法获取,getSystemService()方法接收一个字符串参数用于确定获取系统那一个服务,这里是Context.NOTIFICATION_SERVICE。
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATON_SERVICE);
2.创建Notification对象
创建Notification对象需要使用Builder构造器,但是几乎每个版本都会对Notification进行或多或少的修改,导致API不稳定,所以使用NotificationCompat类的构造器创建Notification对象。(NotificationCompat已被放弃使用)
Notification notification = new NotificationCompat.Builder(context)
.setContentTitle(“123”)
.setContentText(“123”)
.setWhen(System.currentTimeMills())
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(getResource(),R.drawable.large_icon))
.build();
setContentTitle():指定通知栏内容的标题;
setContentText():指定通知栏内容;
setWhen():指定被创建的时间(毫秒为单位);
setSmallIcon():设置通知的小图标;
setLargeIcon():设置通知的大图标
进阶方法:
setSound():接收一个uri参数,设置通知铃声。
.setSound(Uri.fromFile(new File(“/system/media/audio/ringtones/Luna.ogg”)))
setVibrate():接收一个长整型的数组,用于设置手机静止和震动的时长,以毫秒为单位。下标为0的值表示手机静止的时长,下标为1的值表示手机震动的时长,下标为2的值又表示手机手机静止的时长,依次类推。此方法需要添加权限“android.permission.VIBRATE”
.setVibrate(new long[]{0,1000,1000,1000})
setLights():控制手机的LED灯,接收3个参数,第一个参数用于指定LED灯的颜色,第二个参数用于指定LED灯亮起的时长,以毫秒为单位,第三个参数用于指定LED灯暗去的时长,也是可以以毫秒为单位。
.setLights(Color.GREEN,1000,1000)
setDefault():设置默认效果,铃声,震动
.setDefault(NotificationCompat.DEFAULT_ALL)
高级方法:
setStyle():富文本信息,如长段文字.setStyle(new NotificationCompat.BigTextStyle().bigText(“………………”)),显示一张大图
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.big_image)))
setPriority():设置通知的重要程度。接收一个整型参数用于这条通知的重要程度,一共有5个常量值可选:PRIORITY_DEFAULT表示默认的重要程度,和不设置效果是一样的;PRIORITY_MIN表示最低的重要程度,系统可能只会在特定场景才会显示这条通知,比如用于下拉状态栏的时候;PRIORITY_LOW表示较低的重要程度,系统可能会将这类通知缩小,改变其显示的顺序,将其排在更重要的通知之后;PRIORITY_HIGH表示较高的重要程度,系统可能会将这类通知放大,改变其显示的顺序,将其排在比较靠前的位置;PRIORITY_MAX表示最高的重要程度,这类通知必须要让用户立即看到甚至需要用户做出响应操作。
.setPriority(NotificationCompat.PRIORITY_MAX)
3.显示Notification
显示Notification使用NotificationManager类的方法notify(),此方法接收两个参数,第一个参数是id,需要保证每个通知指定的id都是不同的,第二个参数是Notification对象。
manager.notify(1,notification);
4.PendingIntent
PendingIntent和Intent的区别:Intent倾向于立即执行某个意图,PendingIntent倾向于在某个合适的时间执行意图。
获取PendingIntent示例,使用PendingIntent的静态方法获取,根据需要选择getActivity(),getService(),getBroadcast()。这几个方法接收的参数都是相同的,第一个参数Context;第二个参数一般情况用不到,传入0即可;第三个参数是一个Intent对象,通过这个对象构建出PendingIntent的“意图”;第四个参数用于确定PendingIntent的行为,有FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT这四种值可选,通常情况下这个参数传入0。
在NotificationCompat.Builder后面连缀setContentIntent()方法添加PandingIntent对象。
5.点击通知取消
在没有使用setContentIntent()方法的时候,点击通知没有任何反应(此处有点击事件,只是没有相应的动作)。当点击通知后,通知不会消失,会一直显示在状态上。解决方法有两种:一种是在NotificationCompat.Builder后连缀setAutoCancel()方法,一种是显示调用NotificationManager的cancel()方法(需要在执行意图后的Activity或者service中使用NotificationManager调用,传入参数为Notification的id)。
Android蓝牙打印机,带你真正了解各种打印格式
这个小票格式基本就是最常见的了。这里面的各种格式,都可以从蓝牙打印机的API里面找到。蓝牙打印机有好多API,我把常用的给封装了一下:PrintUtils.java
/**
* 复位打印机
*/
public static final byte[] RESET = {0x1b, 0x40};
/**
* 左对齐
*/
public static final byte[] ALIGN_LEFT = {0x1b, 0x61, 0x00};
/**
* 中间对齐
*/
public static final byte[] ALIGN_CENTER = {0x1b, 0x61, 0x01};
/**
* 右对齐
*/
public static final byte[] ALIGN_RIGHT = {0x1b, 0x61, 0x02};
/**
* 选择加粗模式
*/
public static final byte[] BOLD = {0x1b, 0x45, 0x01};
/**
* 取消加粗模式
*/
public static final byte[] BOLD_CANCEL = {0x1b, 0x45, 0x00};
/**
* 宽高加倍
*/
public static final byte[] DOUBLE_HEIGHT_WIDTH = {0x1d, 0x21, 0x11};
/**
* 宽加倍
*/
public static final byte[] DOUBLE_WIDTH = {0x1d, 0x21, 0x10};
/**
* 高加倍
*/
public static final byte[] DOUBLE_HEIGHT = {0x1d, 0x21, 0x01};
/**
* 字体不放大
*/
public static final byte[] NORMAL = {0x1d, 0x21, 0x00};
/**
* 设置默认行间距
*/
public static final byte[] LINE_SPACING_DEFAULT = {0x1b, 0x32};
打印实现
打印小票,当然首先需要连接蓝牙打印机。至于如何扫描打印机,如何连接,这个都是标准的蓝牙方式,网上资料也很多。因为本博文主要关注打印格式,所以这个就不再赘述了。连接打印机后,需要从BluetoothSocket中获取OutputStream。然后接下来都是通过OutputStream来给打印机发送打印指令。
设置打印格式
设置打印格式,就要用到上面封装的那些指令了。
/**
* 设置打印格式
*
* @param command 格式指令
*/
public static void selectCommand(byte[] command) {
try {
outputStream.write(command);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
用法如下:
PrintUtils.selectCommand(PrintUtils.RESET);
PrintUtils.selectCommand(PrintUtils.LINE_SPACING_DEFAULT);
PrintUtils.selectCommand(PrintUtils.ALIGN_CENTER);
PrintUtils.selectCommand(PrintUtils.NORMAL);
打印文字
/**
* 打印文字
*
* @param text 要打印的文字
*/
public static void printText(String text) {
try {
byte[] data = text.getBytes("gbk");
outputStream.write(data, 0, data.length);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
用法如下:
PrintUtils.printText("好吃的牛肉面" + "\n");
打印文字的时候,最后都要手动拼接一个 "\n" 用来换行。
完美吗?
根据上面封装的代码,“貌似”是可以实现所有的打印样式了。是的,没毛病。因为上面既有打印格式的设置,又有打印文字的用法。打印小票是没问题了。but……
有的盆友可能会说,这有啥问题的??? 并且给出了他们认为完美的解释:
PrintUtils.printText("合计 53.50" + "\n");
PrintUtils.printText("抹零 3.50" + "\n");
PrintUtils.printText("项目 数量 金额" + "\n");
可是,完美吗?
你可能觉得人工加空格是可以“实现”需求。but……中间的空格,你知道应该添加多少吗?添加多了或者少了,打印出来的结果都会一塌糊涂!并且注意小票上都是要求对齐的!合计、抹零左侧对齐。金额右侧对齐。项目、数量、金额这三列都要中心对齐。。看到这里,这个人工加空格的做法,还完美吗?
给我一个完美的解释!
是的,我们需要一个完美的解释。到底如何实现上面说的打印两列、打印三列的情况。
首先,讲解之前,先设置几个默认值:
/**
* 打印纸一行最大的字节
*/
private static final int LINE_BYTE_SIZE = 32;
/**
* 打印三列时,中间一列的中心线距离打印纸左侧的距离
*/
private static final int LEFT_LENGTH = 16;
/**
* 打印三列时,中间一列的中心线距离打印纸右侧的距离
*/
private static final int RIGHT_LENGTH = 16;
/**
* 打印三列时,第一列汉字最多显示几个文字
*/
private static final int LEFT_TEXT_MAX_LENGTH = 5;
我们知道,通用的打印纸都是有固定宽度的。经过大量测试,得出打印纸一行的最大字节数是32个字节。那么根据上面的注释,我们可以得到以下结论:
LEFT_LENGTH + RIGHT_LENGTH = LINE_BYTE_SIZE
这是毋庸置疑的。左侧宽度 + 右侧宽度 必须要等于打印纸总宽度。
而且因为打印三列的时候,中间一列是要居中显示的,所以LEFT_LENGTH和RIGHT_LENGTH都必须是总宽度32的一半,也就是必须是16.
那么如何计算某个文字所占的字节数呢?
/**
* 获取数据长度
*
* @param msg
* @return
*/
@SuppressLint("NewApi")
private static int getBytesLength(String msg) {
return msg.getBytes(Charset.forName("GB2312")).length;
}
OK,准备了这么多,海参终于准备好了。接下来就可以准备炒面了~
打印两列
/**
* 打印两列
*
* @param leftText 左侧文字
* @param rightText 右侧文字
* @return
*/
@SuppressLint("NewApi")
public static String printTwoData(String leftText, String rightText) {
StringBuilder sb = new StringBuilder();
int leftTextLength = getBytesLength(leftText);
int rightTextLength = getBytesLength(rightText);
sb.append(leftText);
// 计算两侧文字中间的空格
int marginBetweenMiddleAndRight = LINE_BYTE_SIZE - leftTextLength - rightTextLength;
for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
sb.append(" ");
}
sb.append(rightText);
return sb.toString();
}
那位说话了:“你这代码明明也是手动拼的空格啊,完美个毛啊!”。大兄弟你消消气,这里是通过逻辑进行拼接的空格,不是无脑的拼接。打印两列的步骤如下:
拼接左侧一列的文字
拼接两侧文字中间的空格
拼接右侧一列的文字
关键步骤是计算两侧文字中间的空格。怎么计算呢?很简单,总宽度 - 左侧文字长度 - 右侧文字长度 就是空格的长度。
打印三列
/**
* 打印三列
*
* @param leftText 左侧文字
* @param middleText 中间文字
* @param rightText 右侧文字
* @return
*/
@SuppressLint("NewApi")
public static String printThreeData(String leftText, String middleText, String rightText) {
StringBuilder sb = new StringBuilder();
// 左边最多显示 LEFT_TEXT_MAX_LENGTH 个汉字 + 两个点
if (leftText.length() > LEFT_TEXT_MAX_LENGTH) {
leftText = leftText.substring(0, LEFT_TEXT_MAX_LENGTH) + "..";
}
int leftTextLength = getBytesLength(leftText);
int middleTextLength = getBytesLength(middleText);
int rightTextLength = getBytesLength(rightText);
sb.append(leftText);
// 计算左侧文字和中间文字的空格长度
int marginBetweenLeftAndMiddle = LEFT_LENGTH - leftTextLength - middleTextLength / 2;
for (int i = 0; i < marginBetweenLeftAndMiddle; i++) {
sb.append(" ");
}
sb.append(middleText);
// 计算右侧文字和中间文字的空格长度
int marginBetweenMiddleAndRight = RIGHT_LENGTH - middleTextLength / 2 - rightTextLength;
for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
sb.append(" ");
}
// 打印的时候发现,最右边的文字总是偏右一个字符,所以需要删除一个空格
sb.delete(sb.length() - 1, sb.length()).append(rightText);
return sb.toString();
}
打印三列的步骤如下:
拼接左侧一列的文字
拼接左侧文字和中间文字中间的空格
拼接中间的文字
拼接右侧文字和中间文字中间的空格
拼接右侧一列的文字
在计算空格的时候,为了保证中间一列始终保持中心线对齐,所以在计算中间文字长度时候,都除以2。
完整打印代码
PrintUtils.selectCommand(PrintUtils.RESET);
PrintUtils.selectCommand(PrintUtils.LINE_SPACING_DEFAULT);
PrintUtils.selectCommand(PrintUtils.ALIGN_CENTER);
PrintUtils.printText("美食餐厅\n\n");
PrintUtils.selectCommand(PrintUtils.DOUBLE_HEIGHT_WIDTH);
PrintUtils.printText("桌号:1号桌\n\n");
PrintUtils.selectCommand(PrintUtils.NORMAL);
PrintUtils.selectCommand(PrintUtils.ALIGN_LEFT);
PrintUtils.printText(PrintUtils.printTwoData("订单编号", "201507161515\n"));
PrintUtils.printText(PrintUtils.printTwoData("点菜时间", "2016-02-16 10:46\n"));
PrintUtils.printText(PrintUtils.printTwoData("上菜时间", "2016-02-16 11:46\n"));
PrintUtils.printText(PrintUtils.printTwoData("人数:2人", "收银员:张三\n"));
PrintUtils.printText("--------------------------------\n");
PrintUtils.selectCommand(PrintUtils.BOLD);
PrintUtils.printText(PrintUtils.printThreeData("项目", "数量", "金额\n"));
PrintUtils.printText("--------------------------------\n");
PrintUtils.selectCommand(PrintUtils.BOLD_CANCEL);
PrintUtils.printText(PrintUtils.printThreeData("面", "1", "0.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("米饭", "1", "6.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("铁板烧", "1", "26.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("一个测试", "1", "226.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("牛肉面啊啊", "1", "2226.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("牛肉面啊啊啊牛肉面啊啊啊", "888", "98886.00\n"));
PrintUtils.printText("--------------------------------\n");
PrintUtils.printText(PrintUtils.printTwoData("合计", "53.50\n"));
PrintUtils.printText(PrintUtils.printTwoData("抹零", "3.50\n"));
PrintUtils.printText("--------------------------------\n");
PrintUtils.printText(PrintUtils.printTwoData("应收", "50.00\n"));
PrintUtils.printText("--------------------------------\n");
PrintUtils.selectCommand(PrintUtils.ALIGN_LEFT);
PrintUtils.printText("备注:不要辣、不要香菜");
PrintUtils.printText("\n\n\n\n\n");
学习了上面的打印格式,那么这个小票怎么打印? 区别就是打印三列的时候,中间一列是偏右了。相信大家应该知道答案了。如果有疑问,可以给我留言。
鉴于好多读者给我留言,要PrintUtils工具类代码,所以我把代码发布到github上了,大家可以自行下载。地址是:https://github.com/heroxuetao/PrintUtils
PrintUtils源码
package com.restaurant.diandian.merchant.utils;
import android.annotation.SuppressLint;
import android.text.TextUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
public class PrintUtils {
/**
* 打印纸一行最大的字节
*/
private static final int LINE_BYTE_SIZE = 32;
private static final int LEFT_LENGTH = 20;
private static final int RIGHT_LENGTH = 12;
/**
* 左侧汉字最多显示几个文字
*/
private static final int LEFT_TEXT_MAX_LENGTH = 8;
/**
* 小票打印菜品的名称,上限调到8个字
*/
public static final int MEAL_NAME_MAX_LENGTH = 8;
private static OutputStream outputStream = null;
public static OutputStream getOutputStream() {
return outputStream;
}
public static void setOutputStream(OutputStream outputStream) {
PrintUtils.outputStream = outputStream;
}
/**
* 打印文字
*
* @param text 要打印的文字
*/
public static void printText(String text) {
try {
byte[] data = text.getBytes("gbk");
outputStream.write(data, 0, data.length);
outputStream.flush();
} catch (IOException e) {
//Toast.makeText(this.context, "发送失败!", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
/**
* 设置打印格式
*
* @param command 格式指令
*/
public static void selectCommand(byte[] command) {
try {
outputStream.write(command);
outputStream.flush();
} catch (IOException e) {
//Toast.makeText(this.context, "发送失败!", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
/**
* 复位打印机
*/
public static final byte[] RESET = {0x1b, 0x40};
/**
* 左对齐
*/
public static final byte[] ALIGN_LEFT = {0x1b, 0x61, 0x00};
/**
* 中间对齐
*/
public static final byte[] ALIGN_CENTER = {0x1b, 0x61, 0x01};
/**
* 右对齐
*/
public static final byte[] ALIGN_RIGHT = {0x1b, 0x61, 0x02};
/**
* 选择加粗模式
*/
public static final byte[] BOLD = {0x1b, 0x45, 0x01};
/**
* 取消加粗模式
*/
public static final byte[] BOLD_CANCEL = {0x1b, 0x45, 0x00};
/**
* 宽高加倍
*/
public static final byte[] DOUBLE_HEIGHT_WIDTH = {0x1d, 0x21, 0x11};
/**
* 宽加倍
*/
public static final byte[] DOUBLE_WIDTH = {0x1d, 0x21, 0x10};
/**
* 高加倍
*/
public static final byte[] DOUBLE_HEIGHT = {0x1d, 0x21, 0x01};
/**
* 字体不放大
*/
public static final byte[] NORMAL = {0x1d, 0x21, 0x00};
/**
* 设置默认行间距
*/
public static final byte[] LINE_SPACING_DEFAULT = {0x1b, 0x32};
/**
* 设置行间距
*/
// public static final byte[] LINE_SPACING = {0x1b, 0x32};//{0x1b, 0x33, 0x14}; // 20的行间距(0,255)
// final byte[][] byteCommands = {
// { 0x1b, 0x61, 0x00 }, // 左对齐
// { 0x1b, 0x61, 0x01 }, // 中间对齐
// { 0x1b, 0x61, 0x02 }, // 右对齐
// { 0x1b, 0x40 },// 复位打印机
// { 0x1b, 0x4d, 0x00 },// 标准ASCII字体
// { 0x1b, 0x4d, 0x01 },// 压缩ASCII字体
// { 0x1d, 0x21, 0x00 },// 字体不放大
// { 0x1d, 0x21, 0x11 },// 宽高加倍
// { 0x1b, 0x45, 0x00 },// 取消加粗模式
// { 0x1b, 0x45, 0x01 },// 选择加粗模式
// { 0x1b, 0x7b, 0x00 },// 取消倒置打印
// { 0x1b, 0x7b, 0x01 },// 选择倒置打印
// { 0x1d, 0x42, 0x00 },// 取消黑白反显
// { 0x1d, 0x42, 0x01 },// 选择黑白反显
// { 0x1b, 0x56, 0x00 },// 取消顺时针旋转90°
// { 0x1b, 0x56, 0x01 },// 选择顺时针旋转90°
// };
/**
* 打印两列
*
* @param leftText 左侧文字
* @param rightText 右侧文字
* @return
*/
@SuppressLint("NewApi")
public static String printTwoData(String leftText, String rightText) {
StringBuilder sb = new StringBuilder();
int leftTextLength = getBytesLength(leftText);
int rightTextLength = getBytesLength(rightText);
sb.append(leftText);
// 计算两侧文字中间的空格
int marginBetweenMiddleAndRight = LINE_BYTE_SIZE - leftTextLength - rightTextLength;
for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
sb.append(" ");
}
sb.append(rightText);
return sb.toString();
}
/**
* 打印三列
*
* @param leftText 左侧文字
* @param middleText 中间文字
* @param rightText 右侧文字
* @return
*/
@SuppressLint("NewApi")
public static String printThreeData(String leftText, String middleText, String rightText) {
StringBuilder sb = new StringBuilder();
// 左边最多显示 LEFT_TEXT_MAX_LENGTH 个汉字 + 两个点
if (leftText.length() > LEFT_TEXT_MAX_LENGTH) {
leftText = leftText.substring(0, LEFT_TEXT_MAX_LENGTH) + "..";
}
int leftTextLength = getBytesLength(leftText);
int middleTextLength = getBytesLength(middleText);
int rightTextLength = getBytesLength(rightText);
sb.append(leftText);
// 计算左侧文字和中间文字的空格长度
int marginBetweenLeftAndMiddle = LEFT_LENGTH - leftTextLength - middleTextLength / 2;
for (int i = 0; i < marginBetweenLeftAndMiddle; i++) {
sb.append(" ");
}
sb.append(middleText);
// 计算右侧文字和中间文字的空格长度
int marginBetweenMiddleAndRight = RIGHT_LENGTH - middleTextLength / 2 - rightTextLength;
for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
sb.append(" ");
}
// 打印的时候发现,最右边的文字总是偏右一个字符,所以需要删除一个空格
sb.delete(sb.length() - 1, sb.length()).append(rightText);
return sb.toString();
}
/**
* 获取数据长度
*
* @param msg
* @return
*/
@SuppressLint("NewApi")
private static int getBytesLength(String msg) {
return msg.getBytes(Charset.forName("GB2312")).length;
}
/**
* 格式化菜品名称,最多显示MEAL_NAME_MAX_LENGTH个数
*
* @param name
* @return
*/
public static String formatMealName(String name) {
if (TextUtils.isEmpty(name)) {
return name;
}
if (name.length() > MEAL_NAME_MAX_LENGTH) {
return name.substring(0, 8) + "..";
}
return name;
}
}
用android自带的类库,可以将界面直接打印成PDF文件
//创建PDF文档对象
PdfDocument doc = new PdfDocument();
//设置文档大小,页面位置,内容区域
Size ss = ViewUtil.getScreenSize(this);
PdfDocument.PageInfo.Builder builder = new PdfDocument.PageInfo.Builder(ss.w + 20, ss.h + 40, 1);
Rect rect = new Rect(0, 0, ss.w, ss.h);
builder.setContentRect(rect);
//创建配置信息
PdfDocument.PageInfo info = builder.create();
//创建页面对象
PdfDocument.Page page = doc.startPage(info);
//向页面绘制内容
View rootView = ViewUtil.getRootView(this);
rootView.draw(page.getCanvas());
doc.finishPage(page);
//将PDF文档写入到存储卡Documents公共目录下面
String fileName = "/" + DateUtil.formatDate(new Date(), "yyyy-MM-dd-hhmmss") + ".pdf";
String path = FileUtil.getAndroidPublicDocumentDirectory() + fileName;
doc.writeTo(new FileOutputStream(path));
//关闭文档对象,结束
doc.close();
使用过程中有几点要注意:
1. ScrollView只会打印可见区域
2. ViewGroup会根据总大小打印,即使看不见也会打印
3. 控件必须绘制完成才能打印,否则会打印成空白
4. 如果在onCreate中直接打印,需要延时执行,等待界面加载完成
Android开发之so文件使用方法详解帧的速度。
一、前言
之前自己写了个扒谱助手apk,想把录音得到的pcm转成mp3,百度发现需要使用so文件实现,然后在踩了一堆坑后,终于实现了pcm转mp3的方法。
包含如何生成so文件、如何使用so文件两个主要内容。
现在记录如下。
二、流程
1.目标是android实现pcm转mp3,首先从这个网址找到了大概的方法:
https://blog.csdn.net/u013487404/article/details/86520541
2.从网上下载了lame-3.100.tar.gz,直接解压就行;
lame下载地址:
https://lame.sourceforge.io/
下载android-ndk-r23-windows.zip,解压后,配置个环境变量就行(修改path啥的)
ndk下载地址:
https://developer.android.google.cn/ndk/downloads
ndk环境变量配置样例:
Path F:\android-ndk-r23
3.打开cmd,执行下ndk-build,看看环境变量配好了没有。
4.按照教程,创建一个mp3lame文件夹,例如F:\mp3lame,然后创建个jni文件夹,例如F:\mp3lame\jni,然后把下载的lame源码中的libmp3lame整个拷贝到jni下,例如F:\mp3lame\jni\libmp3lame。
5.然后在jni目录下创建Android.mk文件,位置例如F:\mp3lame\jni\Android.mk,内容例如:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libmp3lame
LOCAL_CFLAGS := -DSTDC_HEADERS
LOCAL_SRC_FILES := \
./libmp3lame/bitstream.c \
./libmp3lame/encoder.c \
./libmp3lame/fft.c \
./libmp3lame/gain_analysis.c \
./libmp3lame/id3tag.c \
./libmp3lame/lame.c \
./libmp3lame/mpglib_interface.c \
./libmp3lame/newmdct.c \
./libmp3lame/presets.c \
./libmp3lame/psymodel.c \
./libmp3lame/quantize.c \
./libmp3lame/quantize_pvt.c \
./libmp3lame/reservoir.c \
./libmp3lame/set_get.c \
./libmp3lame/tables.c \
./libmp3lame/takehiro.c \
./libmp3lame/util.c \
./libmp3lame/vbrquantize.c \
./libmp3lame/VbrTag.c \
./libmp3lame/version.c \
./wrapper.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
6.然后创建Application.mk文件,位置例如:F:\mp3lame\jni\Application.mk,内容例如:
APP_PLATFORM := android-19
7.然后在mp3lame目录下执行ndk-build命令,例如:
F:\mp3lame>ndk-build
.如果报错,看清报错信息,如果报的是缺少lame.h错误,就把lame源码中的lame.h放到jni目录下,例如放到F:\mp3lame\jni\lame.h
9.如果报错jni/util.h:574:5: error: unknown type name ‘ieee754_float32_t‘等这种类型的错误,
就修改jni中的util.h文件,把extern ieee754_float32_t fast_log2(ieee754_float32_t x);替换为extern float fast_log2(float x);
注意该变量可能有多个,
因此要把util.h中的多个地方的ieee754_float32_t都替换为float。
10.编写一个wrapper.c文件,供java实际调用用,位置例如:F:\Z\mp3lame\jni\wrapper.c,代码例如:
#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <android/log.h>
#include "libmp3lame/lame.h"
#define LOG_TAG "LAME ENCODER"
#define LOGD(format, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, format, ##args);
#define BUFFER_SIZE 8192
#define be_short(s) ((short) ((unsigned short) (s) << 8) | ((unsigned short) (s) >> 8))
lame_t lame;
int read_samples(FILE *input_file, short *input) {
int nb_read;
nb_read = fread(input, 1, sizeof(short), input_file) / sizeof(short);
int i = 0;
while (i < nb_read) {
input[i] = be_short(input[i]);
i++;
}
return nb_read;
}
void Java_com_example_myapplication_Record_RecordToMP3_initEncoder(JNIEnv *env,
jobject jobj, jint in_num_channels, jint in_samplerate, jint in_brate,
jint in_mode, jint in_quality) {
lame = lame_init();
LOGD("Encoding Init parameters:");
lame_set_num_channels(lame, in_num_channels);
LOGD("Encoding Number of channels: %d", in_num_channels);
lame_set_in_samplerate(lame, in_samplerate);
LOGD("Encoding Sample rate: %d", in_samplerate);
lame_set_brate(lame, in_brate);
LOGD("Encoding Bitrate: %d", in_brate);
lame_set_mode(lame, in_mode);
LOGD("Encoding Mode: %d", in_mode);
lame_set_quality(lame, in_quality);
LOGD("Encoding Quality: %d", in_quality);
int res = lame_init_params(lame);
LOGD("Encoding Init returned: %d", res);
}
void Java_com_example_myapplication_Record_RecordToMP3_destroyEncoder(
JNIEnv *env, jobject jobj) {
int res = lame_close(lame);
LOGD("Encoding Deinit returned: %d", res);
}
void Java_com_example_myapplication_Record_RecordToMP3_encodeFile(JNIEnv *env,
jobject jobj, jstring in_source_path, jstring in_target_path) {
const char *source_path, *target_path;
source_path = (*env)->GetStringUTFChars(env, in_source_path, NULL);
target_path = (*env)->GetStringUTFChars(env, in_target_path, NULL);
FILE *input_file, *output_file;
input_file = fopen(source_path, "rb");
output_file = fopen(target_path, "wb");
short input[BUFFER_SIZE];
char output[BUFFER_SIZE];
int nb_read = 0;
int nb_write = 0;
int nb_total = 0;
LOGD("Encoding started");
while (nb_read = read_samples(input_file, input)) {
nb_write = lame_encode_buffer(lame, input, input, nb_read, output,
BUFFER_SIZE);
fwrite(output, nb_write, 1, output_file);
nb_total += nb_write;
}
LOGD("Encoded %d bytes", nb_total);
nb_write = lame_encode_flush(lame, output, BUFFER_SIZE);
fwrite(output, nb_write, 1, output_file);
LOGD("Encoded Flushed %d bytes", nb_write);
fclose(input_file);
fclose(output_file);
}
11.这里需要注意下,这个wrapper.c文件中,有3个方法:
Java_com_example_myapplication_Record_RecordToMP3_initEncoder
Java_com_example_myapplication_Record_RecordToMP3_destroyEncoder
Java_com_example_myapplication_Record_RecordToMP3_encodeFile
这3个方法名,是有特殊含义的,不能乱写,需要根据需要自己改。
解释如下:
其中的`com_example_myapplication_Record`是说,我有一个java文件,它在`com.example.myapplication.Record`包里;(这个java文件下面会提到)
`RecordToMP3`是java文件名,对应我的android项目中的RecordToMP3.java文件;
`initEncoder`、`destroyEncoder`、`encodeFile`对应RecordToMP3.java文件中的3个方法名。
这些需要根据自己的需要修改。
对应的java文件下方会讲到。
12.重新在F:\mp3lame下执行ndk-build命令,F:\mp3lame\ndk-build,得到F:\Z\mp3lame\libs文件夹及其中的文件。
13.在自己的Android项目中写一个java文件,包名例如:com.example.myapplication.Record,文件名例如:RecordToMP3.java,内容例如:
package com.example.myapplication.Record;
import android.os.Environment;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
public class RecordToMP3 {
static {
//libmp3lame
System.loadLibrary("mp3lame");
}
private String filePath;
public static final File PATH = Environment.getExternalStorageDirectory();
private String fileName = "txtRecord";
private String fileLast = ".mp3";
public RecordToMP3(){
int numChannels = 2;
int sampleRate = 44100;
int bitRate = 16;
int quality = 2;
int mode = 3;
initMP3();
initEncoder(numChannels, sampleRate, bitRate, mode, quality);
}
public String getFilePath(){
return filePath;
}
/**
*
* @param numChannels 声道数
* @param sampleRate 采样率
* @param bitRate 比特率
* @param mode 模式
* @param quality
*/
public native void initEncoder(int numChannels, int sampleRate, int bitRate, int mode, int quality);
public native void destroyEncoder();
public native int encodeFile(String sourcePath, String targetPath);
public void pcmToMP3(String pcmPath, String mp3Path){
try {
File file1 = bigtolittle(pcmPath);
encodeFile(file1.getPath(),mp3Path);
file1.delete();
}catch (Exception e){ }
}
/**
* 注意:直接转mp3会出现噪音。。因为安卓字节是小端排序,lame是大端排序,所以需要转换,转换代码如下:
*
* 大小端字节转换
* @param fileName
* @return
* @throws IOException
*/
private static File bigtolittle( String fileName) throws IOException {
File file = new File(fileName); //filename为pcm文件,请自行设置
InputStream in = null;
byte[] bytes = null;
in = new FileInputStream(file);
bytes = new byte[in.available()];//in.available()是得到文件的字节数
int length = bytes.length;
while (length != 1) {
long i = in.read(bytes, 0, bytes.length);
if (i == -1) {
break;
}
length -= i;
}
int dataLength = bytes.length;
int shortlength = dataLength / 2;
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, 0, dataLength);
ShortBuffer shortBuffer = byteBuffer.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();//此处设置大小端
short[] shorts = new short[shortlength];
shortBuffer.get(shorts, 0, shortlength);
File file1 = File.createTempFile("pcm", null);//输出为临时文件
String pcmtem = file1.getPath();
FileOutputStream fos1 = new FileOutputStream(file1);
BufferedOutputStream bos1 = new BufferedOutputStream(fos1);
DataOutputStream dos1 = new DataOutputStream(bos1);
for (int i = 0; i < shorts.length; i++) {
dos1.writeShort(shorts[i]);
}
dos1.close();
Log.d("gg", "bigtolittle: " + "=" + shorts.length);
return file1;
}
private void initMP3(){
try {
int i = 0;
filePath = PATH + "/" + fileName + i + fileLast;
File f = new File(filePath);
while (f.exists()) {
i++;
filePath = PATH + "/" + fileName + i + fileLast;
f = new File(filePath);
}
f.createNewFile();
}catch (Exception e){}
}
}
说明:
●static方法中的System.loadLibrary方法会加载lib下的文件(这个下面提到)
●其中3个native方法实际会调用lib中的方法,可以供其它java调用,名称与wrapper.c中的要对应
●这个java使用时,可以new一个对象,调用其中的方法即可,例如:
//pcm文件路径
String filePath = "/abc.pcm";
//pcm文件对象
File filePathFile = new(filePath);
//new对象
RecordToMP3 recordToMP3 = new RecordToMP3();
//pcm转mp3
recordToMP3.pcmToMP3(filePath, recordToMP3.getFilePath());
//得到的mp3的路径
String finalPath = recordToMP3.getFilePath();
//删除pcm文件
filePathFile.delete();
//释放资源
recordToMP3.destroyEncoder();
14.现在,终于讲到so文件使用方法了。
●在android项目中,与java文件夹同级,新建一个libs文件夹;例如F:\myapp\app\src\main\libs
●在这个libs文件夹中,把刚才ndk-build得到的libs文件夹下的内容直接放过来即可。这样so文件就放到android项目中了。路径例如:
F:\myapp\app\src\main\libs\arm64-v8a\libmp3lame.so
F:\myapp\app\src\main\libs\armeabi-v7a\libmp3lame.so
F:\myapp\app\src\main\libs\x86\libmp3lame.so
F:\myapp\app\src\main\libs\x86_64\libmp3lame.so
共4个同名文件,在不同的文件夹中,调用时会自动选择到底用哪一个。
●修改build.gradle文件,位置例如F:\myapp\app\build.gradle,增加如下内容:
sourceSets {
main {
jniLibs.srcDirs = ['src/main/libs']
}
}
整个build.gradle样例:
apply plugin: 'com.android.application'
android {
lintOptions {
checkReleaseBuilds false
abortOnError false
}
compileSdkVersion 28
defaultConfig {
applicationId "com.z.bapuzhushou"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/libs']
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:percent:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
这样,so文件就放入了android项目中,调用native方法就可以使用了。
三、总结
●so文件使用方法:
1.在android项目中,项目名/app/src/main/目录下(与java文件夹同级),创建libs文件夹,在libs文件夹中放so文件(中间可以有x86_64等其它文件夹)
2.修改build.gradle文件,增加sourceSets配置,指明libs的路径。
3.编写java类,使用System.loadLibrary方法读取so文件,使用native方法调用so文件中的方法;需要注意类的包路径、类名、方法名,要与so文件中的方法名保持一致。
4.编写其它java类,调用native方法,就是调用so文件中的方法了
android.mk 和application.mk 详细分析
1.Android.mk
Android.mk是一个 android NDK 构建系统描述NDK项目的makefile 片段。它是每一个NDK项目必备组件。一般来说它与源代码在同一层目录中,下面是一个样例文本:
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= ffmpegutils
LOCAL_SRC_FILES:= com_example_myapplication_MainActivity.c com_example_myapplication_Ffmpeg.c
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/include
LOCAL_LDLIBS:= -L$(LOCAL_PATH) -lm -lz -llog
include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH被用来定位源文件,因为把改变量设置为硬编码值并不可取,所以Android构建系统提供了一个名为my-dir的宏功能。通过该变量设置为my-dir宏功能的返回值。
CLEAR_VAR变量被Android系统构建设置为clear-vars.mk的位置。可用来清除除了LOCAL_PATH以外的LOCAL_XX变量的值。这样做是因为Android.mk中可以同时构建多个共享库,清除它可以避免冲突,如下所示可以构建两个共享库:
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= ffmpegutils
LOCAL_SRC_FILES:= com_example_myapplication_MainActivity.c com_example_myapplication_Ffmpeg.c
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/include
LOCAL_LDLIBS:= -L$(LOCAL_PATH) -lm -lz -llog
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= test
LOCAL_SRC_FILES:= test.c
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/include
LOCAL_LDLIBS:= -L$(LOCAL_PATH) -lm -lz -llog
include $(BUILD_SHARED_LIBRARY)
LOCAL_MODULE用来给这些模块设定一个唯一的名称,也被用来给构建过程所生成的文件命名,构建系统给该文件名添加了适当的前缀和后缀。如模块名为hello,生成的文件名为libhello.so.
LOCAL_SRC_FILES用来建立和组装这个模块的源文件列表,如有多个文件,则文件与文件之间用空格分离。
BUILD_SHARED_LIBRARY被设置成build-shared-library.mk的路径,include $(BUILD_SHARED_LIBRARY)表明生成一个共享库。
include $(BUILD_STATIC_LIBRARY)表明生成一个静态库
其他系统变量:
TARGET_ARCH: 目标体系结构名称,例如arm
TARGET_PLATFROM:目标Android平台名,例如android-3
TARGET_ARCH_ABI:目标CPU体系结构和ABI的名称,例如armeabi-v7s
TARGET_ABI:目标平台和ABI的串联,例如:android-3-armeabi-v7s
LOCAL_MODULE_FILENAME:可选变量,重新定义生成的输出文件名称,如果有定义将覆盖LOCAL_MODULE的生成文件名
LOCAL_CPP_EXTENSION: C++源文件的扩展名,默认cpp,可以指定多个扩展名,用空格分离
LOCAL_CPP_FEATURES: 指明模块依赖的c++特性,如RTTI,exceptions等,例子:LOCAL_CPP_FEATURES :=rtti
LOCAL_C_INCLUDES:可选include路径,NDK安装目录的相对路径
....
LOCAL_C_INCLUDES :=sources/shared-module
LOCAL_C_INCLUDES :=$(LOCAL_PATH)/include
....
LOCAL_CFLAGS: 编译选项,在编译c和c++源文件时会被传送给编译器,如:LOCAL_CFLAGS := -DNDEBUG -DPORT=1234
LOCAL_CPP_FLAGS:编译选项,在编译c++源文件时会被传送给编译器
LOCAL_WHOLE_STATIC_LIBRARIES:LOCAL_STATIC_LIBRARIES的变体,用来指明应该包含在共享库中的所有静态库内容
LOCAL_LDLIBS:链接标志可选列表,该标志将被传送给链接器,如链接日志库,LOCAL_LDFLAGS :=-llog
LOCAL_ALLOW_UNDEFINDED_SYMBOLS:禁止在生成文件中进行缺失符号检查。
LOCAL_ARM_MODE:ARM体系结构特有变量,默认情况下生成16位指令,该变量被设置为arm将生成32位指令,LOCAL_SRC_FILES可指定特定文件为arm指定,如:LOCAL_SRC_FILES :=files.c files.c.arm
LOCAL_ARM_NEON:用来指定源文件应该使用ARM高级单指令多数据流,如LOCAL_ARM_NEON :=true
定义新变量
MY_SRC_FILES :=avilib.c platform.c
LOCAL_SRC_FILES :=$(addprefilx avilib/,$(MY_SRC_FILES))
条件操作
ifeq ($(TARGET_ARCH),arm)
LOCAL_SRC_FILES += armonly.c
else
LOCAL_SRC_FILES += generic.c
endif
...
其他函数宏:
all-subdir-makefiles:返回当前目录的所有子目录下的android.mk文件集
this-makefile: 返回当前Android.mk文件路径
parent-makefile:返回当前构建文件的父Android.mk文件路径
grand-parent-makefile:返回当前构建文件的祖父Android.mk文件路径
最后介绍一个使用共享模块的NDK项目android.mk:
include $(CLEAR_VARS)
LOCAL_MODULES :=module
LOCAL_SRC_FILES :=module.c
LOCAL_SHARED_LIBRARIES :=avilib
include $(BUILD_SHARED_LIBRARY)
$(call import-module,transcode/avilib)
import-module需先定位共享模块,然后再将它导入到ndk项目中,默认情况下,该宏只搜索<android ndk>/source 目录,可以定义一个NDK_MODULE_PATH的环境变量,并将它设成共享目录的根目录。
2.Application.mk
APP_MODULES:模块名,该变量可覆盖android.mk的LOCAL_MODULE声明的名称
APP_OPTIM: 可为release或debug,默认为release
APP_CLAGS,APP_CPP_FLAGS:编译器选项
APP_BUILD_SCRIPT:默认情况,android NDK将查找Android.mk,该变量将改变这一默认行为。
APP_ABI: 构建的指令集,如:APP_ABI :=mips armeabi, APP_ABI:=all
APP_STL:默认情况下,将使用最小的STL运行库,即system库,该变量可选择不同的stl实现,如APP_STL:= stlport_shared
Android打印PDF
manifests文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.qdian.testmyapp" >
<uses-permission android:name="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:requestLegacyExternalStorage="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
界面xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ff0000ff"
android:orientation="vertical"
>
<Button
android:layout_width="match_parent"
android:text="@string/printpdf"
android:layout_height="wrap_content"
android:id="@+id/mybt"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/sample_text"
/>
</LinearLayout>
MainActivity.java文件
package com.qdian.testmyapp;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.pdf.PdfRenderer;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.print.PrintHelper;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
String filePath="";
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE };
public static void verifyStoragePermissions(Activity activity) {
// Check if we have write permission
int permission = ActivityCompat.checkSelfPermission(activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
verifyStoragePermissions(this);
findViewById(R.id.mybt).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
String[] strArray=new String[1];
strArray[0]="application/pdf";
intent.putExtra(Intent.EXTRA_MIME_TYPES,strArray);
intent.setType("*/*");//无类型限制
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, 1);
}
});
}
public static String getPath(Context context, Uri uri) throws URISyntaxException {
if ("content".equalsIgnoreCase(uri.getScheme())) {
String[] projection = { "_data" };
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, projection, null, null, null);
int column_index = cursor.getColumnIndexOrThrow("_data");
if (cursor.moveToFirst()) {
return cursor.getString(column_index);
}
} catch (Exception e) {
// Eat it Or Log it.
}
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
Uri uri = data.getData();
filePath = uri.getPath();
try {
filePath=getPath(MainActivity.this,uri);
} catch (URISyntaxException e) {
e.printStackTrace();
}
Toast.makeText(MainActivity.this, filePath, Toast.LENGTH_LONG).show();
doPrint();
}
}
void doPrint() {
// Get a PrintManager instance
PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
PrintAttributes.Builder builder = new PrintAttributes.Builder();
builder.setColorMode(PrintAttributes.COLOR_MODE_COLOR);
printManager.print("test pdf print", new MyPrintDocumentAdapterPDF(this,filePath), builder.build());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
}
MyPrintDocumentAdapterPDF.java文件
package com.qdian.testmyapp;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.pdf.PdfDocument;
import android.graphics.pdf.PdfRenderer;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.pdf.PrintedPdfDocument;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
public class MyPrintDocumentAdapterPDF extends PrintDocumentAdapter {
private Context context;
private int pageHeight;
private int pageWidth;
private PdfDocument mPdfDocument;
private int totalpages = 1;
private String pdfPath;
private List<Bitmap> mlist;
public MyPrintDocumentAdapterPDF(Context context, String pdfPath) {
this.context = context;
this.pdfPath = pdfPath;
}
@Override
//在onLayout()方法中,你的适配器需要告诉系统框架文本类型,总页数等信息
public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal,
LayoutResultCallback callback,
Bundle metadata) {
mPdfDocument = new PrintedPdfDocument(context, newAttributes); //创建可打印PDF文档对象
pageHeight = newAttributes.getMediaSize().ISO_A4.getHeightMils() * 72 / 1000; //设置尺寸
pageWidth = newAttributes.getMediaSize().ISO_A4.getWidthMils() * 72 / 1000;
if (cancellationSignal.isCanceled()) {
callback.onLayoutCancelled();
return;
}
ParcelFileDescriptor mFileDescriptor = null;
PdfRenderer pdfRender = null;
PdfRenderer.Page page = null;
try {
mFileDescriptor = ParcelFileDescriptor.open(new File(pdfPath), ParcelFileDescriptor.MODE_READ_ONLY);
if (mFileDescriptor != null)
pdfRender = new PdfRenderer(mFileDescriptor);
mlist = new ArrayList<>();
if (pdfRender.getPageCount() > 0) {
totalpages = pdfRender.getPageCount();
for (int i = 0; i < pdfRender.getPageCount(); i++) {
if(null != page)
page.close();
page = pdfRender.openPage(i);
Bitmap bmp = Bitmap.createBitmap(page.getWidth()*2,page.getHeight()*2, Bitmap.Config.ARGB_8888);
page.render(bmp, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
mlist.add(bmp);
}
}
if(null != page)
page.close();
if(null != mFileDescriptor)
mFileDescriptor.close();
if (null != pdfRender)
pdfRender.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (totalpages > 0) {
PrintDocumentInfo.Builder builder = new PrintDocumentInfo
.Builder("快速入门.pdf")
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(totalpages); //构建文档配置信息
PrintDocumentInfo info = builder.build();
callback.onLayoutFinished(info, true);
} else {
callback.onLayoutFailed("Page count is zero.");
}
}
@Override
public void onWrite(final PageRange[] pageRanges, final ParcelFileDescriptor destination, final CancellationSignal cancellationSignal,
final WriteResultCallback callback) {
for (int i = 0; i < totalpages; i++) {
if (pageInRange(pageRanges, i)) //保证页码正确
{
PdfDocument.PageInfo newPage = new PdfDocument.PageInfo.Builder(pageWidth,
pageHeight, i).create();
PdfDocument.Page page =
mPdfDocument.startPage(newPage); //创建新页面
if (cancellationSignal.isCanceled()) { //取消信号
callback.onWriteCancelled();
mPdfDocument.close();
mPdfDocument = null;
return;
}
drawPage(page, i); //将内容绘制到页面Canvas上
mPdfDocument.finishPage(page);
}
}
try {
mPdfDocument.writeTo(new FileOutputStream(
destination.getFileDescriptor()));
} catch (IOException e) {
callback.onWriteFailed(e.toString());
return;
} finally {
mPdfDocument.close();
mPdfDocument = null;
}
callback.onWriteFinished(pageRanges);
}
private boolean pageInRange(PageRange[] pageRanges, int page) {
for (int i = 0; i < pageRanges.length; i++) {
if ((page >= pageRanges[i].getStart()) &&
(page <= pageRanges[i].getEnd()))
return true;
}
return false;
}
//页面绘制(渲染)
private void drawPage(PdfDocument.Page page,int pagenumber) {
Canvas canvas = page.getCanvas();
if(mlist != null){
Paint paint = new Paint();
Bitmap bitmap = mlist.get(pagenumber);
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
// 计算缩放比例
float scale = (float)pageWidth/(float)bitmapWidth;
// 取得想要缩放的matrix参数
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
canvas.drawBitmap(bitmap,matrix,paint);
}
}
}