最近在开发一款报表引擎,里面就需要我会写js做一些简单的混合开发,刚开始学习混合开发的前两天是最郁闷的,毕竟这是一门新的语言,但是还是硬着头皮往上搞了,到第二天的时候就把支持懒加载的Listview倒腾出来了,当然这只是一个最最简单的实现,老手们轻喷,但是对于我这种新手来说绝对是一个重大突破了;
上图,有图有真相;
思路整理
先说一下思路;
- 学习写第一个混合应用(互相调用和传参调用);
- 学习写一个最简单的ListView;
- 实现listView懒加载;
- 通过询问做前端的朋友,了解到了Echart这个工具;
- 在Echarts官网学习了简单的使用(我这里是折线图入手的),并修改一些参数观察变化;
- 学习写一个简单的图表列表实现;
这篇文章我只介绍到如何实现一个简单的ListView,所以只介绍如何实现前三步,其实知道了前三步之后又我就开始对做一个混合的报表应用有点信心了,我相信小伙伴们去做了之后也会有同样的感触;
实现步骤
分三步实现,尽可能的让新手能看懂(我觉得没问题,因为我就是菜鸟),往上有挺多的教程的,不过我感觉还是不够全面吧,淡然我讲的也不一定全面,但是按照这个做绝对能实现;
Step1 混合应用入门
首先明确的是,怎么样才算是一个混合应用,我的定义是既有原生代码又有Web前端代码,而且这两者还能交互(通讯);通讯是最重要的,没有通讯那跟单独写没两样,也就算不上做了混合开发;因此这个环节我觉得最重要的是学会通讯;
通讯就是互相调用嘛,调用的时候可能要带一些参数并拿一些返回值,所以思路就是这样,1. 互相调用,分为带参数和不带参数;2.互相调用,分为带参数有返回值;3.js从安卓取复杂数据
大家直接看代码吧,在座的各位都是精英,我就不多说了;
- 布局文件中就一个Webview:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/ll_btn"
android:layout_gravity="center" />
</RelativeLayout>
- 在java代码中找到这个Webview平配置相关参数;
private WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadWeb();
}
private void loadWeb() {
mWebView = findViewById(R.id.webView);
//支持html中javascript解析,不管是不是js和android交互,只要网页中含有js都要
mWebView.getSettings().setJavaScriptEnabled(true);
//通过获取asset文件,为了方便我把html放在asserts里面了,通过网络获取是一样的
String url = "file:///android_asset/index.html";
mWebView.loadUrl(url);
// 这个很重要了,相当于给js传了一个对象引用,js拿到这个对象可以操作这个JsIntegration对象里面的方法
// 大家可以想想一下jni开发,是不是有点有点像,后面跟的"android",是可以自定义的,你写成啥,在js脚本使用的时候就是啥
// 比如我这里是android,那么js中使用就是【window.android.方法名】
mWebView.addJavascriptInterface(new JsIntegration(),"android");
}
JsIntegration的实现很简单,就是实现了Js调用Java带参、不带参、带返回、不带返回;
/**
* 这个类是专门提供给js调用的
*
* @author larsonzhong
*/
public class JsIntegration {
// 带@JavascriptInterface声明的方法才能被js调用到
// 这个是不带参的调用
@JavascriptInterface
public void sayHello() {
Log.d("JsIntegration", "java Code:hello world !!!");
}
// 这个是带参的调用
@JavascriptInterface
public void setCode(int state) {
return "java Code:" + state;
}
// 这个是不带参带返回的调用
@JavascriptInterface
public String getHelloString() {
return "java Code:hello world !!!";
}
// 这个是带参带返回的调用
@JavascriptInterface
public String getConvertedString(String jsStr) {
return "js jsStr:" + jsStr;
}
// 用这个打印日志到logcat
@JavascriptInterface
public void printLog(String logText){
Log.d("JsIntegration",logText+"");
}
// 这个js从android获取复杂对象的调用
@JavascriptInterface
public String getList() {
// 此处省略lsit的赋值
Gson gson2=new Gson();
String str=gson2.toJson(list);
return "js jsStr:" + jsStr;
}
}
注释写的很明白了我这里就不多说了,我们来看下在js脚本中是怎么调用的;首先我们需要一个html页面;
<!DOCTYPE html>
<html>
<head>
<title>测试</title>
</head>
<body onLoad="init();">
<div id="temp">h5页面</div>
<button id="btn" onclick="getNativeMsg()">获取NativeString</button>
</body>
<script type="text/javascript">
// 这些是页面加载的时候就调用的
//1. 无参无返回值的方法
window.android.sayHello();
//2. 有参无返回值的方法
window.android.setCode(200);
//3. 不带参带返回的调用
var helloText =window.android.getHelloString();
console.log(helloText);// 输出helloText信息
//3. 带参带返回的调用
var message =window.android.getConvertedString("is from js");
alert(message);// 弹窗显示message信息
//通过方法传字符串
function changeText(textString){
document.getElementById("temp").innerHTML = textString;
}
//通过方法传复杂对象,实际上是转换成String传递,然后再通过json转换为对象
function handleComplexObject(jsonStr){
var obj = JSON.parse(jsonCharts);
if (obj &&typeof option ==="object") {
window.android.printLog("show end :"+jsonCharts.toString());
}
}
</script>
</html>
实现动态加载ListView
通过上面一个例子我们已经入门了如何做混合开发,接下来我们要动态从安卓的java代码中取数据,为了方便,我简化了java的代码逻辑,直接用写死的数据,不过我相信大家一定能够灵活应用,比如从数据库获取其他地方获取数据,因此就尽可能的简单;
在我看来,尽量把业务逻辑交给App来实现是最好的,因为相对于js,java代码的效率更高,而且如果是从android转向js开发,上手起来也更加容易;
界面部分就不多说了,还是用的一个布局里面一个Webview;
为了开发方便,我把Webview自定义了一个,里面封装了一些通用配置;
/**
* 避免重复代码,WEb的配置索性就自定义个View来解决
*
* @author larsonzhong@163.com
*/
public class EchartView extends WebView {
public EchartView(Context context) {
this(context, null);
}
public EchartView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EchartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@SuppressLint("SetJavaScriptEnabled")
private void init() {
WebSettings webSettings = getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setSupportZoom(false);
webSettings.setDisplayZoomControls(false);
loadUrl("file:///android_asset/echarts.html");
}
public void startLoadCharts() {
String call = "javascript:startLoadCharts()";
// 调用loadUrl方法调用js的startLoadCharts();
loadUrl(call);
}
}
那么Js中的startLoadCharts方法怎么写呢?(下面的代码我只考虑实现,关于线程同步的我就没考虑了);
echarts.html
<body style="height: 100%; margin: 0">
<!-- 我在最开头放一张图片,ID就叫head_background -->
<img id="head_background" src="images/background.jpg"/>
function startLoadCharts(){
// 首先知道有多少个数据
var dataSize = window.android.getJsonSize();
// 占位,给每个位置分配对应的id,以便于在数据获取到之后能够对应填充,反过来数,因为插入时最后插入的在最前面
for (var i = (infos.length-1); i >=0; i--) {
// 大家发现我这里面有一个data-attribute,这个是自定义属性,因为我要做懒加载,所以需要知道哪些加载过了,这样可以避免重复刷新
// 新数据
$("#head_background").after("<div id='chart_div_"+i+"' data-attribute=0 style='height: 40%' ></div>");
}
// 懒加载,要先调用一次,不然要等用户刷了才出来
lazyload();
}
大家注意到我这里面写了一个懒加载方法,但是我没有把代码贴出来,懒加载是个什么原理呢?简单的来说就是如果这个数据还没加载到界面上,而此时用户还没滑到这个地方,那么这个地方的数据可以暂时不加载,当用户滑到这个位置,发现这个位置的数据没有加载,那就调用显示的方法把数据加载上来,这样可以尽可能的避免界面卡顿,提升交互体验;
那么具体的实现就很简单了;
懒加载
function lazyload(){
// 每次滑动就会触发lazyload方法,此时我需要去遍历哪些还没有加载,所以先拿到有多少个数据
var jsonSize = window.android.getJsonSize();
var seeHeight = document.documentElement.clientHeight; //可见区域高度
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
// 遍历每个标签
for(var i = 0; i < jsonSize; i++) {
var dom =document.getElementById("chart_div_"+i);
// 这里就是判断当前标签是否处于可见范围内
if (dom.offsetTop < seeHeight + scrollTop) {
// 拿到标签的是否加载标志
var domValue = dom.dataset.attribute;
// 0表示还没有加载(初始值)
if (domValue == 0) {
// 給自定義屬性賦值,1表示已經賦值
dom.setAttribute("data-attribute", 1);
//然后我们就可以拿到当前标签对应的数据(可以是复杂对象转成的json)
var jsonCharts = window.android.getJsonAt(i);
// js提供了json转换方法JSON.parse;,这样我们拿到了对象option
var option = JSON.parse(jsonCharts);
if (option &&typeof option ==="object") {
// todo这个时候我们就可以拿到对象的信息,然后可以为所欲为了
}
}else{
// 表示已经加载过了,不需要处理
}
}
}
}
// 简单的节流函数
//fun 要执行的函数
//delay 延迟
//time 在time时间内必须执行一次
function throttle(fun, delay, time) {
var timeout,
startTime = new Date();
return function() {
var context = this,
args = arguments,
curTime = new Date();
clearTimeout(timeout);
// 如果达到了规定的触发时间间隔,触发 handler
if (curTime - startTime >= time) {
fun.apply(context, args);
startTime = curTime;
// 没达到触发间隔,重新设定定时器
} else {
timeout = setTimeout(function(){
fun.apply(context, args);
}, delay);
}
};
};
// 实际想绑定在 scroll 事件上的 handler
//function lazyload(event) {}
// 采用了节流函数
window.addEventListener('scroll',throttle(lazyload,500,1000));
到了这里我们就实现了一个支持内存优化的混合listview,很简单啊有没有;
ps:这个就不要找我要代码了,真的是超级简单了;
有用的话记得点赞哦