
匠心出品,跨平台前端组件库FirstUI 1.2.0正式发布
FirstUI(https://www.firstui.cn/)是基于uni-app开发的一款轻量、全面可靠的跨平台移动端组件库。包括框架、组件、模板、功能插件几个部分。FirstUI开发者、设计师不断精心打磨,持续发布新的组件、模板等新功能,力求为用户提供更高品质的产品,节约用户时间与成本。
一、FirstUI特性
● 多端支持。一套代码,多端适用,支持iOS(vue和Nvue)、Android(vue和Nvue)、微信小程序、支付宝小程序、QQ小程序、百度小程序、字节跳动小程序、H5平台
● 完善的组件。目前共规划118款,已上线70款,涵盖基础组件、表单组件、导航组件、布局组件、常用布局、扩展组件、操作反馈、数据组件、JS、图表、画布。
● 丰富实用的布局、模板。基于FirstUI提供的组件,针对常用场景、行业,提供丰富实用的布局和模板。
● 专属社区。我们打造了FirstU专属社区,用户可以在社区交流分享FirstUI的使用经验、提问。有其他组件、模板需求,也可以在社区中反馈。
二、FirstUI体系进展

三、扫码体验FirstUI
考虑快速预览,所以暂未上架App应用,后续待功能完善再进行上架。
四、开源版与商业版
FirstUI分为开源版与商业版,部分组件为商业版专属使用。
1、开源版
● github: https://github.com/FirstUI/FirstUI (欢迎star :-D)
● gitee: https://gitee.com/firstui/FirstUI (欢迎star :-D)
● 文档地址: https://doc.firstui.cn
2、VIP会员权益:
● 完整版源码
● 全部组件
● 物料商城享VIP折扣
● 专属会员群指导、答疑
● 新特性优先体验
● VIP专属文档
会员权益详情: https://www.firstui.cn/right
3、新版优惠
新版发布,优惠期内框架可5折¥150元购买(原价¥300元),2022年01月31日截止。购买框架产品即升级为VIP会员,享受VIP会员权益。
立即购买:https://www.firstui.cn/store/detail/1
FirstUI(https://www.firstui.cn/)是基于uni-app开发的一款轻量、全面可靠的跨平台移动端组件库。包括框架、组件、模板、功能插件几个部分。FirstUI开发者、设计师不断精心打磨,持续发布新的组件、模板等新功能,力求为用户提供更高品质的产品,节约用户时间与成本。
一、FirstUI特性
● 多端支持。一套代码,多端适用,支持iOS(vue和Nvue)、Android(vue和Nvue)、微信小程序、支付宝小程序、QQ小程序、百度小程序、字节跳动小程序、H5平台
● 完善的组件。目前共规划118款,已上线70款,涵盖基础组件、表单组件、导航组件、布局组件、常用布局、扩展组件、操作反馈、数据组件、JS、图表、画布。
● 丰富实用的布局、模板。基于FirstUI提供的组件,针对常用场景、行业,提供丰富实用的布局和模板。
● 专属社区。我们打造了FirstU专属社区,用户可以在社区交流分享FirstUI的使用经验、提问。有其他组件、模板需求,也可以在社区中反馈。
二、FirstUI体系进展
三、扫码体验FirstUI
考虑快速预览,所以暂未上架App应用,后续待功能完善再进行上架。
四、开源版与商业版
FirstUI分为开源版与商业版,部分组件为商业版专属使用。
1、开源版
● github: https://github.com/FirstUI/FirstUI (欢迎star :-D)
● gitee: https://gitee.com/firstui/FirstUI (欢迎star :-D)
● 文档地址: https://doc.firstui.cn
2、VIP会员权益:
● 完整版源码
● 全部组件
● 物料商城享VIP折扣
● 专属会员群指导、答疑
● 新特性优先体验
● VIP专属文档
会员权益详情: https://www.firstui.cn/right
3、新版优惠
新版发布,优惠期内框架可5折¥150元购买(原价¥300元),2022年01月31日截止。购买框架产品即升级为VIP会员,享受VIP会员权益。
立即购买:https://www.firstui.cn/store/detail/1

说真的。文档体验绝了
说真的。文档体验绝了,真的绝了。
我们用开源的东西本不该开口索要的,uni-app也算是你们的一个产品了。 你们也有商业模式,能否将文档好好整理一下, 文档体验简直不要太好
说真的。文档体验绝了,真的绝了。
我们用开源的东西本不该开口索要的,uni-app也算是你们的一个产品了。 你们也有商业模式,能否将文档好好整理一下, 文档体验简直不要太好

tag的监听事件方法。
代码渲染的titleNView 里的tag如何监听。代码如下。
假设你的tags里有一个子tag left 距离是30,宽度是130。
//那么就先定义监听区域
plus.webview.currentWebview().getTitleNView().setTouchEventRect({top:'0px',left:'30px',width:'130px',height:'100%'})
//创建监听。
plus.webview.currentWebview().getTitleNView().addEventListener("click",function(e){ })
注:setTouchEventRect 可以传数组。多个区域监听。
有问题欢迎留言,大家一起讨论。
代码渲染的titleNView 里的tag如何监听。代码如下。
假设你的tags里有一个子tag left 距离是30,宽度是130。
//那么就先定义监听区域
plus.webview.currentWebview().getTitleNView().setTouchEventRect({top:'0px',left:'30px',width:'130px',height:'100%'})
//创建监听。
plus.webview.currentWebview().getTitleNView().addEventListener("click",function(e){ })
注:setTouchEventRect 可以传数组。多个区域监听。
有问题欢迎留言,大家一起讨论。

uniapp直接跳转应用商店
// 跳转应用商店
export const jumpToAppMarket = (code) => {
// 可以根據文檔換成其他商店 https://www.jianshu.com/p/b544810beac3
const googlePlay = "com.android.vending";
if (plus.os.name == "Android") {
var Uri = plus.android.importClass("android.net.Uri");
var Intent = plus.android.importClass('android.content.Intent');
var main = plus.android.runtimeMainActivity();
var uri = Uri.parse("market://details?id=" + 包名);
var intent = new Intent(Intent.ACTION_VIEW, uri);
// 选择进入商店
intent.setPackage(googlePlay);
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK;
// 没有该商店应用
if (intent.resolveActivity(main.getPackageManager()) !== null) {
main.startActivity(intent);
} else {
// 跳转浏览器
let uri = Uri.parse("https://play.google.com/store/apps/details?id=" + 包名);
let intent = new Intent(Intent.ACTION_VIEW, uri);
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setPackage('com.android.browser');
main.startActivity(intent);
}
} else {
plus.runtime.openURL('itms-apps://itunes.apple.com/cn/app/id{appid}?mt=8');
}
}
// 跳转应用商店
export const jumpToAppMarket = (code) => {
// 可以根據文檔換成其他商店 https://www.jianshu.com/p/b544810beac3
const googlePlay = "com.android.vending";
if (plus.os.name == "Android") {
var Uri = plus.android.importClass("android.net.Uri");
var Intent = plus.android.importClass('android.content.Intent');
var main = plus.android.runtimeMainActivity();
var uri = Uri.parse("market://details?id=" + 包名);
var intent = new Intent(Intent.ACTION_VIEW, uri);
// 选择进入商店
intent.setPackage(googlePlay);
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK;
// 没有该商店应用
if (intent.resolveActivity(main.getPackageManager()) !== null) {
main.startActivity(intent);
} else {
// 跳转浏览器
let uri = Uri.parse("https://play.google.com/store/apps/details?id=" + 包名);
let intent = new Intent(Intent.ACTION_VIEW, uri);
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setPackage('com.android.browser');
main.startActivity(intent);
}
} else {
plus.runtime.openURL('itms-apps://itunes.apple.com/cn/app/id{appid}?mt=8');
}
}
收起阅读 »
基于uniapp打包生成app,并引入本地原生插件步骤和踩坑
看到社区很多同学对于uniapp和原生混合开发配置问题,就怎么引入原生本地插件并配置运行步骤详解
1.首先在你的文件夹下建nativeplugins目录,将本地开发的原生插件代码包按照格式放入
2.对nativeplugins目录下的pack.json进行配置 注意name和id要与目录名保持一致,class要安卓小伙伴给你,包名 类名
3.在manifest.json进行插件引入
第四步:点击运行到手机或模拟器,先制定基座,制作完成后在运行基座选择自定义基座,进行打包即可
看到社区很多同学对于uniapp和原生混合开发配置问题,就怎么引入原生本地插件并配置运行步骤详解
1.首先在你的文件夹下建nativeplugins目录,将本地开发的原生插件代码包按照格式放入
2.对nativeplugins目录下的pack.json进行配置 注意name和id要与目录名保持一致,class要安卓小伙伴给你,包名 类名
3.在manifest.json进行插件引入
第四步:点击运行到手机或模拟器,先制定基座,制作完成后在运行基座选择自定义基座,进行打包即可
收起阅读 »

录音支持暂停、继续、后台录音、息屏录音(ios、andorid)
录音支持暂停、继续、后台录音、息屏录音(ios、andorid) :https://ext.dcloud.net.cn/plugin?id=5849
录音支持暂停、继续、后台录音、息屏录音(ios、andorid) :https://ext.dcloud.net.cn/plugin?id=5849

强制安卓进入前台运行,完整代码及遇到的坑
最近突发奇想,想做一个给自己用的app,主要时做一些个人记录和强制停止无脑刷屏==。
在每次解锁手机或刷视频超过一定时间,后台就把自己开发的app进入前台,提醒自己的目标及可以做其他的事有什么。
网上的资料并不能完整的解决问题,然后就自己撸了一下代码。
todo 之后有空的话把这部分打包成一个uniapp的插件。
完整代码
Timer topTimer=null;
public void startTopTimer(){
stopTopTimer();
topTimer=new Timer();
TimerTask timerTask=new TimerTask() {
@Override
public void run() {
activityInTop();
}
};
topTimer.schedule(timerTask,0,500);
}
public void stopTopTimer(){
if(topTimer!=null){
topTimer.cancel();
topTimer=null;
}
}
public void activityInTop() {
/**获取ActivityManager*/
ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(ACTIVITY_SERVICE);
/**获得当前运行的task(任务)*/
int nowPosition=0;
List<ActivityManager.RunningTaskInfo> taskInfoList = activityManager.getRunningTasks(100);
for (ActivityManager.RunningTaskInfo taskInfo : taskInfoList) {
/**找到本应用的 task,并将它切换到前台*/
if (taskInfo.topActivity.getPackageName().equals(getApplicationContext().getPackageName())) {
if(nowPosition==0){
stopTopTimer();
return;
}
activityManager.moveTaskToFront(taskInfo.id, 0);
Log.d(TAG, "找到本应用的 task,并将它切换到前台");
return;
}
nowPosition++;
}
// 应用需要置顶且前台应用被关闭时,重新打开应用
Intent intent=getPackageManager().getLaunchIntentForPackage(getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
使用
startTopTimer();
碰到的坑
应用被关闭时,service需要拉起一个新的应用
// 应用需要置顶且前台应用被关闭时,重新打开应用
Intent intent=getPackageManager().getLaunchIntentForPackage(getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
moveTaskToFront需要在应用进入后台5-7s后执行
解决方案:执行moveTaskToFront方法后,判断是否成功。不成功每间隔500ms再执行一次,直到成功为止。
最近突发奇想,想做一个给自己用的app,主要时做一些个人记录和强制停止无脑刷屏==。
在每次解锁手机或刷视频超过一定时间,后台就把自己开发的app进入前台,提醒自己的目标及可以做其他的事有什么。
网上的资料并不能完整的解决问题,然后就自己撸了一下代码。
todo 之后有空的话把这部分打包成一个uniapp的插件。
完整代码
Timer topTimer=null;
public void startTopTimer(){
stopTopTimer();
topTimer=new Timer();
TimerTask timerTask=new TimerTask() {
@Override
public void run() {
activityInTop();
}
};
topTimer.schedule(timerTask,0,500);
}
public void stopTopTimer(){
if(topTimer!=null){
topTimer.cancel();
topTimer=null;
}
}
public void activityInTop() {
/**获取ActivityManager*/
ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(ACTIVITY_SERVICE);
/**获得当前运行的task(任务)*/
int nowPosition=0;
List<ActivityManager.RunningTaskInfo> taskInfoList = activityManager.getRunningTasks(100);
for (ActivityManager.RunningTaskInfo taskInfo : taskInfoList) {
/**找到本应用的 task,并将它切换到前台*/
if (taskInfo.topActivity.getPackageName().equals(getApplicationContext().getPackageName())) {
if(nowPosition==0){
stopTopTimer();
return;
}
activityManager.moveTaskToFront(taskInfo.id, 0);
Log.d(TAG, "找到本应用的 task,并将它切换到前台");
return;
}
nowPosition++;
}
// 应用需要置顶且前台应用被关闭时,重新打开应用
Intent intent=getPackageManager().getLaunchIntentForPackage(getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
使用
startTopTimer();
碰到的坑
应用被关闭时,service需要拉起一个新的应用
// 应用需要置顶且前台应用被关闭时,重新打开应用
Intent intent=getPackageManager().getLaunchIntentForPackage(getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
moveTaskToFront需要在应用进入后台5-7s后执行
解决方案:执行moveTaskToFront方法后,判断是否成功。不成功每间隔500ms再执行一次,直到成功为止。
收起阅读 »
Android 音乐通知栏Native.js版本!
代码
const androidNotifi = {
NotifyID: 237,
receiver:null,
NotifiService:null,
serv: null,
intent: null,
mNotificationBuild: null,
mediaBtn: null,
channelID : '1',
channelName : '音乐通知',
notifiImg:{},
getImgTemp: function(audioImage) {
return new Promise(resolve => {
if (this.notifiImg[encodeURIComponent(audioImage)]) {
return resolve(plus.io.convertLocalFileSystemURL(this.notifiImg[encodeURIComponent(audioImage)]));
}
if(!/^http/.test(audioImage)) {
this.notifiImg[encodeURIComponent(audioImage)] = audioImage ? audioImage : '_www/static/image/notifimusic.png';
return resolve(plus.io.convertLocalFileSystemURL(audioImage ? audioImage : '_www/static/image/notifimusic.png'));
}
uni.downloadFile({
url: audioImage,
complete: res => {
this.notifiImg[encodeURIComponent(audioImage)] = res.tempFilePath ? res.tempFilePath : '_www/static/image/notifimusic.png';
return resolve(plus.io.convertLocalFileSystemURL(res.tempFilePath ? res.tempFilePath : '_www/static/image/notifimusic.png'));
}
});
});
},
hideNotifi: function(server = false){
this.main&&this.receiver&&this.main.unregisterReceiver(this.receiver);
server&&this.serv&&this.serv.stopForeground&&this.serv.stopForeground(this.NotifyID, this.mNotificationBuild) ;
if (Number(plus.os.version.split('.')[0])>= 8){
this.NotifiService&&this.NotifiService.cancel&&this.NotifiService.cancel(this.channelID,this.NotifyID);
} else {
this.NotifiService&&this.NotifiService.cancel&&this.NotifiService.cancel(this.NotifyID);
}
this.receiver = null;
this.NotifiService = null;
this.serv = null;
this.intent = null;
this.mNotificationBuild = null;
// this.NotifiService&&this.NotifiService.cancelAll&&this.NotifiService.cancelAll();
this.mediaBtn ? this.mediaBtn.release() : ''; // 线控销毁
},
showNotifi:async function(param) {
if (uni.$_sko.systemInfo.platform != 'android') {
return;
}
try{
param = Object.assign({
title : '通知标题',
subTitle: '',
cont : '通知内容',
isPlay: true, // 媒体播放按钮
legIcon : plus.io.convertLocalFileSystemURL('_www/static/image/notifimusic.png'),
Ongoing : false, // 不可删除
},param);
this.main = this.main ? this.main : plus.android.runtimeMainActivity();
var Context = plus.android.importClass("android.content.Context");
var BitmapFactory = plus.android.importClass("android.graphics.BitmapFactory");
var NotificationManager = plus.android.importClass("android.app.NotificationManager");
var Notification = plus.android.importClass("android.app.Notification");;
var Intent = plus.android.importClass("android.content.Intent");
var PendingIntent = plus.android.importClass("android.app.PendingIntent");
var androidR = plus.android.importClass("android.R");
this.NotifiService = this.NotifiService ? this.NotifiService : this.main.getSystemService(Context.NOTIFICATION_SERVICE);
this.intent = this.intent ? this.intent : new Intent(this.main, this.main.getClass());
var UNI_MEDIA_BROAD = this.main.getPackageName() + '.mediaBtn';
//PendingIntent.getActivity的第二个参数需要设置为随机数,否则多个通知时会导致前面的通知被后面的通知替换Extra的数据
var pendingIntent = PendingIntent.getActivity(this.main, 0, this.intent, PendingIntent.FLAG_CANCEL_CURRENT);
var firstVersionNumber = Number(plus.os.version.split('.')[0]);
var mNotification;
//判断当前系统版本在8.0及以上
if (firstVersionNumber >= 8){
if (this.NotifiService.getNotificationChannel() == null){
var NotificationChannel = plus.android.importClass('android.app.NotificationChannel');
var channel = new NotificationChannel(this.channelID, this.channelName, NotificationManager.IMPORTANCE_HIGH);
this.NotifiService.createNotificationChannel(channel);
plus.android.autoCollection(NotificationChannel);
}
param.callBack&&this.main.startForegroundService(this.intent);
mNotification = new Notification.Builder(this.main, this.channelID);
} else {
mNotification = new Notification.Builder(this.main);
param.callBack&&this.main.startService(this.intent);
}
if(param.callBack) {
mNotification.addAction(androidR.drawable.ic_media_previous, "prev", PendingIntent.getBroadcast(this.main, 0, new Intent().setAction(UNI_MEDIA_BROAD + '.prev'), PendingIntent.FLAG_CANCEL_CURRENT));
mNotification.addAction((param.isPlay ? androidR.drawable.ic_media_pause : androidR.drawable.ic_media_play), "play", PendingIntent.getBroadcast(this.main, 0, new Intent().setAction(UNI_MEDIA_BROAD + '.play'), PendingIntent.FLAG_CANCEL_CURRENT));
mNotification.addAction(androidR.drawable.ic_media_next, "next", PendingIntent.getBroadcast(this.main, 0, new Intent().setAction(UNI_MEDIA_BROAD + '.next'), PendingIntent.FLAG_CANCEL_CURRENT));
var mMediaStyle = new Notification.MediaStyle();
mMediaStyle.setShowActionsInCompactView(0, 1, 2);
mNotification.setStyle(mMediaStyle);
}
mNotification.setOngoing(param.Ongoing);
param.legIcon&&mNotification.setLargeIcon(BitmapFactory.decodeFile(await this.getImgTemp(param.legIcon)));
mNotification.setSmallIcon(param.callBack?androidR.drawable.stat_sys_headset:androidR.drawable.stat_notify_chat); //设置图标
mNotification.setContentTitle(param.title);//设置标题
mNotification.setContentText(param.cont); //设置内容
param.subTitle&&mNotification.setSubText(param.subTitle); //子内容暂时去掉
// mNotification.setAutoCancel(true); //设置点击消失
// mNotification.setShowWhen(true); //显示通知时间
mNotification.setTicker("PadInfo"); //弹出通知
mNotification.setDefaults(Notification.DEFAULT_ALL);
// mNotification.setPriority(Notification.PRIORITY_DEFAULT); //通知优先级
// mNotification.flags=Notification.FLAG_ONLY_ALERT_ONCE; //发起通知时震动
mNotification.setContentIntent(pendingIntent);
this.mNotificationBuild = mNotification.build();
if (firstVersionNumber>= 8){
this.NotifiService.notify(this.channelID, this.NotifyID, this.mNotificationBuild);
} else {
this.NotifiService.notify(this.NotifyID, this.mNotificationBuild);
}
if(param.callBack) {
this.main&&this.receiver&&this.main.unregisterReceiver(this.receiver);
this.receiver = this.receiver ? this.receiver : plus.android.implements('io.dcloud.android.content.BroadcastReceiver', {
onReceive: function(context, intent) { //实现onReceiver回调函数
param.callBack(intent.getAction().replace(UNI_MEDIA_BROAD + '.',''));
}
});
var IntentFilter = plus.android.importClass('android.content.IntentFilter');
var filter = new IntentFilter();
filter.addAction(UNI_MEDIA_BROAD + '.prev');
filter.addAction(UNI_MEDIA_BROAD + '.play');
filter.addAction(UNI_MEDIA_BROAD + '.next');
this.main.unregisterReceiver(this.receiver);
this.main.registerReceiver(this.receiver, filter); //注册监听
this.mediaBtn = uni.requireNativePlugin('uniMediaButton'); // 监听线控 需要配合附件中的插件( Android API >= 26)
this.mediaBtn.initMediaButton(res => {
console.log(res.message);
// 绑定失败/销毁
if(res.code == '-1') {
this.mediaBtn = null;
return;
}
// res.keyAction == 1 按键松开 res.keyAction == 0 按键按下 播放按钮肯能只有按下
if(res.keyCode && res.keyAction == '0') {
switch (res.keyCode){
case 88:
param.callBack(
'prev'
);
break;
case 87:
param.callBack(
'next'
);
break;
case 127:
case 126:
param.callBack(
'play'
);
break;
}
}
});
var serv = plus.android.importClass("android.app.Service");
this.serv = this.serv ? this.serv : new serv();
this.serv.startForeground(this.NotifyID, this.mNotificationBuild);
plus.android.autoCollection(serv);
plus.android.autoCollection(IntentFilter);
}
plus.android.autoCollection(Context);
plus.android.autoCollection(BitmapFactory);
plus.android.autoCollection(NotificationManager);
plus.android.autoCollection(Notification);
plus.android.autoCollection(Intent);
plus.android.autoCollection(PendingIntent);
plus.android.autoCollection(androidR);
plus.android.autoCollection(this.receiver);
}catch(e){
console.log(e);
}
}
};
使用方法
// 开启 - 更新标题/播放(暂停)按钮
show();
function show(){
androidNotifi.showNotifi({
title : '通知标题',
subTitle: '通知小标题',
cont : '通知内容',
isPlay: true, // 媒体播放按钮状态(控制播放/暂停按钮样式)
legIcon : '', // 封面图片
Ongoing : false, // 是否可删除
callBack:function(res){
switch (res){
case 'play':
console.log('play');
break;
case 'prev':
console.log('prev');
break;
case 'next':
console.log('next');
break;
}
}
})
}
// 关闭
function hide(){
androidNotifi.hideNotifi();
}
由于Android播放视频没有通知栏控制.开始一直使用XZH-musicNotification 的插件,但是由于版本更新,出现BUG(插件已更新).
但是不能总等插件更新,就自己动动手写了一个Native.js版本,由于手上设备不多,没有测试更多的兼容性.在这里希望大家能勇于使用修改提出问题,完善此通知提示!
附件是线控耳机、蓝牙耳机按键监听插件(API >= 26,X86好像不支持忘了)
代码
const androidNotifi = {
NotifyID: 237,
receiver:null,
NotifiService:null,
serv: null,
intent: null,
mNotificationBuild: null,
mediaBtn: null,
channelID : '1',
channelName : '音乐通知',
notifiImg:{},
getImgTemp: function(audioImage) {
return new Promise(resolve => {
if (this.notifiImg[encodeURIComponent(audioImage)]) {
return resolve(plus.io.convertLocalFileSystemURL(this.notifiImg[encodeURIComponent(audioImage)]));
}
if(!/^http/.test(audioImage)) {
this.notifiImg[encodeURIComponent(audioImage)] = audioImage ? audioImage : '_www/static/image/notifimusic.png';
return resolve(plus.io.convertLocalFileSystemURL(audioImage ? audioImage : '_www/static/image/notifimusic.png'));
}
uni.downloadFile({
url: audioImage,
complete: res => {
this.notifiImg[encodeURIComponent(audioImage)] = res.tempFilePath ? res.tempFilePath : '_www/static/image/notifimusic.png';
return resolve(plus.io.convertLocalFileSystemURL(res.tempFilePath ? res.tempFilePath : '_www/static/image/notifimusic.png'));
}
});
});
},
hideNotifi: function(server = false){
this.main&&this.receiver&&this.main.unregisterReceiver(this.receiver);
server&&this.serv&&this.serv.stopForeground&&this.serv.stopForeground(this.NotifyID, this.mNotificationBuild) ;
if (Number(plus.os.version.split('.')[0])>= 8){
this.NotifiService&&this.NotifiService.cancel&&this.NotifiService.cancel(this.channelID,this.NotifyID);
} else {
this.NotifiService&&this.NotifiService.cancel&&this.NotifiService.cancel(this.NotifyID);
}
this.receiver = null;
this.NotifiService = null;
this.serv = null;
this.intent = null;
this.mNotificationBuild = null;
// this.NotifiService&&this.NotifiService.cancelAll&&this.NotifiService.cancelAll();
this.mediaBtn ? this.mediaBtn.release() : ''; // 线控销毁
},
showNotifi:async function(param) {
if (uni.$_sko.systemInfo.platform != 'android') {
return;
}
try{
param = Object.assign({
title : '通知标题',
subTitle: '',
cont : '通知内容',
isPlay: true, // 媒体播放按钮
legIcon : plus.io.convertLocalFileSystemURL('_www/static/image/notifimusic.png'),
Ongoing : false, // 不可删除
},param);
this.main = this.main ? this.main : plus.android.runtimeMainActivity();
var Context = plus.android.importClass("android.content.Context");
var BitmapFactory = plus.android.importClass("android.graphics.BitmapFactory");
var NotificationManager = plus.android.importClass("android.app.NotificationManager");
var Notification = plus.android.importClass("android.app.Notification");;
var Intent = plus.android.importClass("android.content.Intent");
var PendingIntent = plus.android.importClass("android.app.PendingIntent");
var androidR = plus.android.importClass("android.R");
this.NotifiService = this.NotifiService ? this.NotifiService : this.main.getSystemService(Context.NOTIFICATION_SERVICE);
this.intent = this.intent ? this.intent : new Intent(this.main, this.main.getClass());
var UNI_MEDIA_BROAD = this.main.getPackageName() + '.mediaBtn';
//PendingIntent.getActivity的第二个参数需要设置为随机数,否则多个通知时会导致前面的通知被后面的通知替换Extra的数据
var pendingIntent = PendingIntent.getActivity(this.main, 0, this.intent, PendingIntent.FLAG_CANCEL_CURRENT);
var firstVersionNumber = Number(plus.os.version.split('.')[0]);
var mNotification;
//判断当前系统版本在8.0及以上
if (firstVersionNumber >= 8){
if (this.NotifiService.getNotificationChannel() == null){
var NotificationChannel = plus.android.importClass('android.app.NotificationChannel');
var channel = new NotificationChannel(this.channelID, this.channelName, NotificationManager.IMPORTANCE_HIGH);
this.NotifiService.createNotificationChannel(channel);
plus.android.autoCollection(NotificationChannel);
}
param.callBack&&this.main.startForegroundService(this.intent);
mNotification = new Notification.Builder(this.main, this.channelID);
} else {
mNotification = new Notification.Builder(this.main);
param.callBack&&this.main.startService(this.intent);
}
if(param.callBack) {
mNotification.addAction(androidR.drawable.ic_media_previous, "prev", PendingIntent.getBroadcast(this.main, 0, new Intent().setAction(UNI_MEDIA_BROAD + '.prev'), PendingIntent.FLAG_CANCEL_CURRENT));
mNotification.addAction((param.isPlay ? androidR.drawable.ic_media_pause : androidR.drawable.ic_media_play), "play", PendingIntent.getBroadcast(this.main, 0, new Intent().setAction(UNI_MEDIA_BROAD + '.play'), PendingIntent.FLAG_CANCEL_CURRENT));
mNotification.addAction(androidR.drawable.ic_media_next, "next", PendingIntent.getBroadcast(this.main, 0, new Intent().setAction(UNI_MEDIA_BROAD + '.next'), PendingIntent.FLAG_CANCEL_CURRENT));
var mMediaStyle = new Notification.MediaStyle();
mMediaStyle.setShowActionsInCompactView(0, 1, 2);
mNotification.setStyle(mMediaStyle);
}
mNotification.setOngoing(param.Ongoing);
param.legIcon&&mNotification.setLargeIcon(BitmapFactory.decodeFile(await this.getImgTemp(param.legIcon)));
mNotification.setSmallIcon(param.callBack?androidR.drawable.stat_sys_headset:androidR.drawable.stat_notify_chat); //设置图标
mNotification.setContentTitle(param.title);//设置标题
mNotification.setContentText(param.cont); //设置内容
param.subTitle&&mNotification.setSubText(param.subTitle); //子内容暂时去掉
// mNotification.setAutoCancel(true); //设置点击消失
// mNotification.setShowWhen(true); //显示通知时间
mNotification.setTicker("PadInfo"); //弹出通知
mNotification.setDefaults(Notification.DEFAULT_ALL);
// mNotification.setPriority(Notification.PRIORITY_DEFAULT); //通知优先级
// mNotification.flags=Notification.FLAG_ONLY_ALERT_ONCE; //发起通知时震动
mNotification.setContentIntent(pendingIntent);
this.mNotificationBuild = mNotification.build();
if (firstVersionNumber>= 8){
this.NotifiService.notify(this.channelID, this.NotifyID, this.mNotificationBuild);
} else {
this.NotifiService.notify(this.NotifyID, this.mNotificationBuild);
}
if(param.callBack) {
this.main&&this.receiver&&this.main.unregisterReceiver(this.receiver);
this.receiver = this.receiver ? this.receiver : plus.android.implements('io.dcloud.android.content.BroadcastReceiver', {
onReceive: function(context, intent) { //实现onReceiver回调函数
param.callBack(intent.getAction().replace(UNI_MEDIA_BROAD + '.',''));
}
});
var IntentFilter = plus.android.importClass('android.content.IntentFilter');
var filter = new IntentFilter();
filter.addAction(UNI_MEDIA_BROAD + '.prev');
filter.addAction(UNI_MEDIA_BROAD + '.play');
filter.addAction(UNI_MEDIA_BROAD + '.next');
this.main.unregisterReceiver(this.receiver);
this.main.registerReceiver(this.receiver, filter); //注册监听
this.mediaBtn = uni.requireNativePlugin('uniMediaButton'); // 监听线控 需要配合附件中的插件( Android API >= 26)
this.mediaBtn.initMediaButton(res => {
console.log(res.message);
// 绑定失败/销毁
if(res.code == '-1') {
this.mediaBtn = null;
return;
}
// res.keyAction == 1 按键松开 res.keyAction == 0 按键按下 播放按钮肯能只有按下
if(res.keyCode && res.keyAction == '0') {
switch (res.keyCode){
case 88:
param.callBack(
'prev'
);
break;
case 87:
param.callBack(
'next'
);
break;
case 127:
case 126:
param.callBack(
'play'
);
break;
}
}
});
var serv = plus.android.importClass("android.app.Service");
this.serv = this.serv ? this.serv : new serv();
this.serv.startForeground(this.NotifyID, this.mNotificationBuild);
plus.android.autoCollection(serv);
plus.android.autoCollection(IntentFilter);
}
plus.android.autoCollection(Context);
plus.android.autoCollection(BitmapFactory);
plus.android.autoCollection(NotificationManager);
plus.android.autoCollection(Notification);
plus.android.autoCollection(Intent);
plus.android.autoCollection(PendingIntent);
plus.android.autoCollection(androidR);
plus.android.autoCollection(this.receiver);
}catch(e){
console.log(e);
}
}
};
使用方法
// 开启 - 更新标题/播放(暂停)按钮
show();
function show(){
androidNotifi.showNotifi({
title : '通知标题',
subTitle: '通知小标题',
cont : '通知内容',
isPlay: true, // 媒体播放按钮状态(控制播放/暂停按钮样式)
legIcon : '', // 封面图片
Ongoing : false, // 是否可删除
callBack:function(res){
switch (res){
case 'play':
console.log('play');
break;
case 'prev':
console.log('prev');
break;
case 'next':
console.log('next');
break;
}
}
})
}
// 关闭
function hide(){
androidNotifi.hideNotifi();
}
由于Android播放视频没有通知栏控制.开始一直使用XZH-musicNotification 的插件,但是由于版本更新,出现BUG(插件已更新).
但是不能总等插件更新,就自己动动手写了一个Native.js版本,由于手上设备不多,没有测试更多的兼容性.在这里希望大家能勇于使用修改提出问题,完善此通知提示!
附件是线控耳机、蓝牙耳机按键监听插件(API >= 26,X86好像不支持忘了)

银联云闪付小程序开发
分享一下我们开发的云闪付小程序PHP后端开发包,unionpay 云闪付小程序开发包
银联云闪付小程序非官方开发包,这可能是第一个支持composer导入的云闪付小程序开发包,小程序后端相关接口与支付相关接口已全部更新完毕。
码云地址:https://gitee.com/leapy/unionpay
Github地址:https://github.com/ileapy/unionpay
vue前端开发包(不建议这样引用)
> npm install upsdk-vue
有需要公众号和小程序开发的可以联系我,我们有团队:
邮箱:cfn@leapy.cn
微信:SH-CFN
分享一下我们开发的云闪付小程序PHP后端开发包,unionpay 云闪付小程序开发包
银联云闪付小程序非官方开发包,这可能是第一个支持composer导入的云闪付小程序开发包,小程序后端相关接口与支付相关接口已全部更新完毕。
码云地址:https://gitee.com/leapy/unionpay
Github地址:https://github.com/ileapy/unionpay
vue前端开发包(不建议这样引用)
> npm install upsdk-vue
有需要公众号和小程序开发的可以联系我,我们有团队:
邮箱:cfn@leapy.cn
微信:SH-CFN

在uniapp中使用Native.js 使用字节流读写文件工具类封装
/**
- @description: gpb文件读写工具 采用Native.js
- @author: Marco
-
@email: wtz_xupt@126.com
*/
export default class GpbFileUtil {
/**-
单例模式 获取实例
*/
static getIntance(){
if(!GpbFileUtil.instance){
GpbFileUtil.instance = new GpbFileUtil()
}return GpbFileUtil.instance
}/**
-
构造函数
*/
constructor(){
this.init()
}/**
-
初始化
*/
init(){
//导入类需要时间,统一导入提高效率
this.main = plus.android.runtimeMainActivity()
this.Environment = plus.android.importClass('android.os.Environment')
this.File = plus.android.importClass('java.io.File')
this.FileInputStream = plus.android.importClass("java.io.FileInputStream")
this.FileOutputStream = plus.android.importClass('java.io.FileOutputStream')
}/**
-
判断有没有插入sdcard
*/
checkSDCardExist(){
if(this.Environment.getExternalStorageState() !== this.Environment.MEDIA_MOUNTED){
return false
}
return true
}/**
-
获取sdcard根路径
*/
getSDCardRoot(){
return this.Environment.getExternalStorageDirectory()
}/**
- 判断sdcard 文件是否存在
- @param {Object} filePath
-
@param {Object} fileName
*/
checkSDCardFileExist(filePath,fileName){
let fileFullPath = this.getSDCardRoot() + this.File.separator + filePath + this.File.separator + fileName
let file = new this.File(fileFullPath)
return file.exists()
}/**
- 删除sdcard中的文件
- @param {Object} filePath 文件路径
-
@param {Object} fileName 文件名称
*/
deleteSDCardFile(filePath,fileName){
let fileFullPath = this.getSDCardRoot() + this.File.separator + filePath + this.File.separator + fileNamelet file = new this.File(fileFullPath) if(file.exists()){ file.delete() }
}
/**
- 读取sdcard中文件内容
- @param {Object} filePath 文件路径
-
@param {Object} fileName 文件名称
*/
readFileFromSDCard(filePath,fileName){
if(!this.checkSDCardFileExist(filePath,fileName)){
plus.nativeUI.toast(fileName + '文件不存在!')
return;
}
let fileFullPath = this.getSDCardRoot() + this.File.separator + filePath + this.File.separator + fileNamelet file = new this.File(fileFullPath)
let fis = new this.FileInputStream(file)
let bytes = new Uint8Array(fis.available())
try{
let index = 0
let byte
while((byte = fis.read()) !== -1){
bytes[index] = byte
++index
}}catch(e){
//TODO handle the exception
console.log('读取sd文件' + fileName + '失败:' + e.message)
}finally{
try{
if(fis !== null){
fis.close()
}
}catch(ex){
//TODO handle the exception
console.log('关闭文件输入流失败:' + ex.message)
}
}return bytes
}/**
- 向sdcard中写文件
- @param {Object} filePath 文件路径
- @param {Object} fileName 文件名称
- @param {Object} fileContentBytes 文件内容字节数组
*/
writeFileToSDCard(filePath,fileName,fileContentBytes){
this.deleteSDCardFile()
let fileFullPath = this.getSDCardRoot() + this.File.separator + filePath + this.File.separator + fileName
let file = new this.File(fileFullPath)
let fos = new this.FileOutputStream(file)
try{
let index = 0
while(index < fileContentBytes.length){
fos.write(fileContentBytes[index])
++index
}
//fos.write(fileContentBytes) //java byte数组在nativejs中写入不了,只能循环一个一个字节写入
fos.flush()
}catch(e){
//TODO handle the exception
console.log('写入sd文件' + fileName + '失败:' + e.message)
}finally{
try{
if(fos !== null){
fos.close()
}
}catch(ex){
//TODO handle the exception
console.log('关闭文件输出流失败:' + ex.message)
}
}
}
}
-
在测试过程中nativejs 调用java FileInputStream read(byte []) 和write(byte []) 读取和写入不了。尝试循环后每次读取或写入一个字节成功了。
/**
- @description: gpb文件读写工具 采用Native.js
- @author: Marco
-
@email: wtz_xupt@126.com
*/
export default class GpbFileUtil {
/**-
单例模式 获取实例
*/
static getIntance(){
if(!GpbFileUtil.instance){
GpbFileUtil.instance = new GpbFileUtil()
}return GpbFileUtil.instance
}/**
-
构造函数
*/
constructor(){
this.init()
}/**
-
初始化
*/
init(){
//导入类需要时间,统一导入提高效率
this.main = plus.android.runtimeMainActivity()
this.Environment = plus.android.importClass('android.os.Environment')
this.File = plus.android.importClass('java.io.File')
this.FileInputStream = plus.android.importClass("java.io.FileInputStream")
this.FileOutputStream = plus.android.importClass('java.io.FileOutputStream')
}/**
-
判断有没有插入sdcard
*/
checkSDCardExist(){
if(this.Environment.getExternalStorageState() !== this.Environment.MEDIA_MOUNTED){
return false
}
return true
}/**
-
获取sdcard根路径
*/
getSDCardRoot(){
return this.Environment.getExternalStorageDirectory()
}/**
- 判断sdcard 文件是否存在
- @param {Object} filePath
-
@param {Object} fileName
*/
checkSDCardFileExist(filePath,fileName){
let fileFullPath = this.getSDCardRoot() + this.File.separator + filePath + this.File.separator + fileName
let file = new this.File(fileFullPath)
return file.exists()
}/**
- 删除sdcard中的文件
- @param {Object} filePath 文件路径
-
@param {Object} fileName 文件名称
*/
deleteSDCardFile(filePath,fileName){
let fileFullPath = this.getSDCardRoot() + this.File.separator + filePath + this.File.separator + fileNamelet file = new this.File(fileFullPath) if(file.exists()){ file.delete() }
}
/**
- 读取sdcard中文件内容
- @param {Object} filePath 文件路径
-
@param {Object} fileName 文件名称
*/
readFileFromSDCard(filePath,fileName){
if(!this.checkSDCardFileExist(filePath,fileName)){
plus.nativeUI.toast(fileName + '文件不存在!')
return;
}
let fileFullPath = this.getSDCardRoot() + this.File.separator + filePath + this.File.separator + fileNamelet file = new this.File(fileFullPath)
let fis = new this.FileInputStream(file)
let bytes = new Uint8Array(fis.available())
try{
let index = 0
let byte
while((byte = fis.read()) !== -1){
bytes[index] = byte
++index
}}catch(e){
//TODO handle the exception
console.log('读取sd文件' + fileName + '失败:' + e.message)
}finally{
try{
if(fis !== null){
fis.close()
}
}catch(ex){
//TODO handle the exception
console.log('关闭文件输入流失败:' + ex.message)
}
}return bytes
}/**
- 向sdcard中写文件
- @param {Object} filePath 文件路径
- @param {Object} fileName 文件名称
- @param {Object} fileContentBytes 文件内容字节数组
*/
writeFileToSDCard(filePath,fileName,fileContentBytes){
this.deleteSDCardFile()
let fileFullPath = this.getSDCardRoot() + this.File.separator + filePath + this.File.separator + fileName
let file = new this.File(fileFullPath)
let fos = new this.FileOutputStream(file)
try{
let index = 0
while(index < fileContentBytes.length){
fos.write(fileContentBytes[index])
++index
}
//fos.write(fileContentBytes) //java byte数组在nativejs中写入不了,只能循环一个一个字节写入
fos.flush()
}catch(e){
//TODO handle the exception
console.log('写入sd文件' + fileName + '失败:' + e.message)
}finally{
try{
if(fos !== null){
fos.close()
}
}catch(ex){
//TODO handle the exception
console.log('关闭文件输出流失败:' + ex.message)
}
}
}
}
-
在测试过程中nativejs 调用java FileInputStream read(byte []) 和write(byte []) 读取和写入不了。尝试循环后每次读取或写入一个字节成功了。
收起阅读 »