Android自動更新:這里的更新靜悄悄~
(第一篇文章沒有把樣式弄好,所以重新弄了一遍,歡迎小伙伴讀這篇文章:Android自動更新:這里的更新靜悄悄~ - 簡書)
產品:APP的底部按鈕能夠做到自動更新嗎?
累人猿:有些麻煩(無辜臉)~
產品:那京東、美團是怎么做到的?
累人猿:……
心疼自己十分鐘~
經常看到有什么活動的時候,京東、淘寶、美團這些應用都不需要更新應用就能夠實現應用UI的更新,特別是底部菜單按鈕的圖標,及時的營造出活動的氣氛。作為一個移動開發者都想弄明白他們是怎么做到的,我所了解的方式有以下三種:
1、使用前端框架,如果是這種情況的話,整個應用都沒有多少原生的東西,比較火的框架:React Native、ionic等;
2、純原生,這種方式是通過圖片下載,本地圖片讀取,動態生成StateListDrawable,大致這樣一個流程實現不更新應用就更新底部按鈕的圖標;
3、JS交互:將整個底部按鈕做成H5,然后使用webview加載,這也是今天的重點。
其中第二和第三種方式,必須得做好容錯處理,比如網絡問題導致圖片下載失敗,本地圖片丟失等問題,為了避免這些問題導致應用的不正常運行,我們需要有備選方案,一旦問題出現,就放棄整個流程,從本地讀取資源。
我并不是前端開發者,h5相關的東西學得不多,所以在實現這個功能的過程中還是花了一些功夫的,JS和CSS用得也不熟,所以在處理有些細節的時候,用得方式有些暴力,希望大伙兒能夠理解,有考慮使用這種方式的小伙伴可以讓h5開發的小伙伴來做。
整個功能是基于JS交互這個基礎上的,至于需要怎么交互,我就不做過多的講解了,不清楚的小伙伴可以去網上找一些資料來學學。
這里的排版沒有弄好,小伙伴們可以到這里去看這篇文章:blog.csdn.net/zhimingshangyan/article/details/52767939
一、CSS布局
整個底部按鈕的界面,都是一些html標簽,我使用了比較經典的四個按鈕,如果有特別需求的小伙伴,可以自己增加或者刪除幾個標簽,源碼如下:
<div>
<label?onclick="myFun('img_one')">
<input ?type="radio"?id="one"?name="tabBtn"?checked>
<img?id="img_one">
<p?style="color: #f00;"></p>
</label>
<label?onclick="myFun('img_two')">
<input?type="radio"?id="two"?name="tabBtn">
<img?id="img_two">
<p></p>
</label>
<label?onclick="myFun('img_three')">
<input?type="radio"id="three"name="tabBtn">
<img?id="img_three">
<p></p>
</label>
<label?onclick="myFun('img_four')">
<input?type="radio"id="four"name="tabBtn">
<img?id="img_four">
<p></p>
</label>
</div>
當然,要實現整個界面方式不止一種,我采用的是radio的方式,接著我們講講css中比較關鍵的地方,第一label的p標簽設置了文字顏色為紅色,是為了初始化,一般應進入應用第一個按鈕是選中狀態,當然這個工作可以放在JS中來做,和圖片初始化一起做,如果設計不是紅色的小伙伴就要注意了,在使用的時候需要修改成設計要求的顏色。
label的CSS代碼:
label{
margin-top:0px;
margin-bottom: -3px;
padding-top:5px;
display:inline-block;
width:25%;
font-weight:normal;
vertical-align:middle;
cursor:pointer;
float:left;
text-align:center;
}
一定要注意25%,因為是四個按鈕,我們需要平分100%,所以每個按鈕的范圍是25%,如果按鈕的個數修改一定要注意這個百分比的修改。
因為使用的webview加載h5的方式,細心的小伙伴會發現,我長按某個按鈕的時候可以復制文本的,所以為了禁用復制功能,我們需要在CSS中做一些限制
/*禁止長按復制功能
*/*{
-webkit-touch-callout:none;
-webkit-user-select:none;
-khtml-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
user-select:none;
}
其實就做了一件事,需要這么些代碼是因為兼容不懂瀏覽器內核。關于div、p、body、input的CSS代碼這里就不詳細講解了,這幾個標簽的CSS代碼可以不做任何修改,直接使用,如果要改的話可以讓h5的小伙伴幫忙改得更規范一些。
二、JS交互
不管是ios還是android都提供了Webview和h5交互的方法。
我們先來看看本地提供給JS的接口方法:
/*
js調用本地的方法改變底部按鈕的選中狀態原理:js調用本地方法,方法內部通過廣播的方式傳遞選中按鈕的編號
*/@JavascriptInterface
public voidchangeTab(String index) {
Intent intent =newIntent();
intent.setAction("ChangeTab");
intent.putExtra("index", index);
context.sendBroadcast(intent);
}
/*將Json數組傳遞給h5頁面
*/@JavascriptInterface
publicString getImages(List lists) {
try{
JSONArray array =newJSONArray();
for(Item item : lists) {
JSONObject object =newJSONObject();
object.put("icon_nor", item.getIcon_nor());
object.put("icon_sel", item.getIcon_sel());
object.put("title", item.getTitle());
array.put(object);
}
String json = array.toString();
returnjson;
}catch(Exception e) {
e.printStackTrace();
}
return"";
}
第一個方法的作用是,在h5界面點擊某個按鈕的使用,js調用本地的方法實現原生fragment的切換,我使用了廣播的方式,來通知原生界面的切換操作。
第二個方法,是為了初始化h5界面,通過js調用這個方法,將數據傳遞給h5頁面,達到初始化界面的效果。
其中Item是我將每個按鈕封裝成了一個對象,這樣方便數據的讀取和傳遞,源碼如下:
public classItem {
privateStringicon_nor;//圖標正常狀態privateStringicon_sel;//圖標選中狀態privateStringtitle;//按鈕文案publicString getIcon_nor() {
returnicon_nor;
}
public voidsetIcon_nor(String icon_nor) {
this.icon_nor= icon_nor;
}
publicString getIcon_sel() {
returnicon_sel;
}
public voidsetIcon_sel(String icon_sel) {
this.icon_sel= icon_sel;
}
publicString getTitle() {
returntitle;
}
public voidsetTitle(String title) {
this.title= title;
}
}
其實這個對象中還應該增加一個顏色變量,這樣的話,我們不需要修改h5的顏色,直接通過傳值來改變顏色,有興趣的小伙伴可以嘗試一下,一定要記得在Js中獲取顏色和應用顏色。
不如我們先來看看應用截圖吧!
這是最正常的情況,也就是說,服務器正常開啟,圖片地址正確傳遞。
這種情況是服務器關閉的情況,看著并沒有什么區別,因為我做了容錯處理,其實這種情況圖片是讀取失敗的,如果沒有容錯處理,圖片會顯示一個錯誤圖片的圖標。
這種情況是,我故意少傳了一張圖片的運行界面,細心的小伙伴會發現這張圖中的tab后面的編號跟前面一張的圖片不一樣,其實這也算是一種保底的做法,一旦出錯,就放棄方法,采用本地的不就方案,保證應用的運行沒有問題。
繼續回到源碼分析,在點擊某個tab的時候,相應ui也應該發生變化,還記得剛剛我們在原生提供的第一個方法嗎,現在就是使用它的時候,
/*通過js調用本地方法,改變tab選中狀態
*/varoneRB=document.getElementById("one");
oneRB.addEventListener('click',function() {
if(oneRB.checked) {
appNative.changeTab('one');
}
},false);
vartwoRB=document.getElementById("two");
twoRB.addEventListener('click',function() {
if(twoRB.checked) {
appNative.changeTab('two');
}
},false);
varthreeRB=document.getElementById("three");
threeRB.addEventListener('click',function() {
if(threeRB.checked) {
appNative.changeTab('three');
}
},false);
varfourRB=document.getElementById("four");
fourRB.addEventListener('click',function() {
if(fourRB.checked) {
appNative.changeTab('four');
}
},false);
啟動應用的時候,我們需要初始化整個tab界面,第二個方法排上用場了,
/*初始化數據,為了排除網絡等不確定因素,需要在images文件夾下面放一套缺省圖標
*/vardatas;
varisOk=true;
varicons=newArray();
varicon1=newObject();
icon1.icon_sel="images/icon_tab_one_sel.png";
icon1.icon_nor="images/icon_tab_one_nor.png";
icon1.title="tab0";
icons.push(icon1);
varicon2=newObject();
icon2.icon_sel="images/icon_tab_two_sel.png";
icon2.icon_nor="images/icon_tab_two_nor.png";
icon2.title="tab1";
icons.push(icon2);
varicon3=newObject();
icon3.icon_sel="images/icon_tab_three_sel.png";
icon3.icon_nor="images/icon_tab_three_nor.png";
icon3.title="tab2";
icons.push(icon3);
varicon4=newObject();
icon4.icon_sel="images/icon_tab_four_sel.png";
icon4.icon_nor="images/icon_tab_four_nor.png";
icon4.title="tab3";
icons.push(icon4);
varoImg=document.getElementsByTagName('img');
functiontranData(jsondata) {
datas=eval(jsondata);
if(isEmpty(jsondata)){
isOk=false;
}else{
for(i=0;i<datas.length;i++){
if(typeofdatas[i].icon_sel==="undefined"||typeofdatas[i].icon_nor==="undefined"){
isOk=false;
}
}
}
if(isOk){
oImg[0].src=datas[0].icon_sel;
oImg[0].nextSibling.nextSibling.innerText=datas[0].title;
oImg[0].onerror=function(){
oImg[0].src=icons[0].icon_sel;
isOk=false;
}
oImg[1].src=datas[1].icon_nor;
oImg[1].nextSibling.nextSibling.innerText=datas[1].title;
oImg[1].onerror=function(){
oImg[1].src=icons[1].icon_nor;
isOk=false;
}
oImg[2].src=datas[2].icon_nor;
oImg[2].nextSibling.nextSibling.innerText=datas[2].title;
oImg[2].onerror=function(){
oImg[2].src=icons[2].icon_nor;
isOk=false;
}
oImg[3].src=datas[3].icon_nor;
oImg[3].nextSibling.nextSibling.innerText=datas[3].title;
oImg[3].onerror=function(){
oImg[3].src=icons[3].icon_nor;
isOk=false;
}
}else{
oImg[0].src=icons[0].icon_sel;
oImg[0].nextSibling.nextSibling.innerText=icons[0].title;
oImg[1].src=icons[1].icon_nor;
oImg[1].nextSibling.nextSibling.innerText=icons[1].title;
oImg[2].src=icons[2].icon_nor;
oImg[2].nextSibling.nextSibling.innerText=icons[2].title;
oImg[3].src=icons[3].icon_nor;
oImg[3].nextSibling.nextSibling.innerText=icons[3].title;
}
}
這段處理有些復雜,因為需要考慮容錯方法,所以我建議小伙伴們,把整個html放在app的assets文件夾下面,不要放在服務端,同時我們還要再assets文件夾下面放一套默認圖標,我個人認為圖標更新失敗,比應用不能夠正常使用要有好的多,建議采用如下的方式,使用這種方案,主要的思路就是不管什么原因導致遠程圖片讀取失敗,都直接使用本地的缺省圖片,還要注意,是整套圖片都使用本地的,不能夠某一張讀取失敗了,只替換某一張噻~
建議使用的小伙伴不要把你們的圖片改成截圖中的名字,這樣就不需要再html源碼中修改。
然后就是處理某個tab選中之后的樣式修改了。
/*改變選中按鈕的樣式和狀態
*/functionmyFun(sId) {
for(vari=0;i<oImg.length;i++) {
iconSelect(i,sId);
}
}
functioniconSelect(i, sId){
if(oImg[i].id== sId) {
oImg[i].previousSibling.previousSibling.checked=true;
oImg[i].nextSibling.nextSibling.style.color="red";
if(isOk){
oImg[i].src=datas[i].icon_sel;
}else{
oImg[i].src=icons[i].icon_sel;
}
}else{
if(isOk){
oImg[i].src=datas[i].icon_nor;
}else{
oImg[i].src=icons[i].icon_nor;
}
oImg[i].nextSibling.nextSibling.style.color="black";
}
}
另外,有時候我們會遇到在某個具體的fragment中點擊某個按鈕跳到其它tab頁面中去,比如在fragment中我點擊了按鈕,需要跳到第二個tab頁面,所以我通過js提供了一個切換tab的方法。
/*提供給原生的方法,原生調用該方法,實現設置某個tab選中
*/functionsetChecked(id) {
varbtn=document.getElementById(id);
btn.checked=true;
if(typeofappNative !=="undefined"&& appNative.changeTab) {
appNative.changeTab(id);
myFun("img_"+ id);
}
}
使用的時候只需要原生中調用,方法如下:
webView.loadUrl("javascript:setChecked('four')");
其中的”four”對應某個需要跳轉到的tab編號,注意只能是“one”、“two”、“three”、“four”
前面就是整個功能的js代碼,我知道,有些功能是完全可以用CSS來實現的,CSS我還不是很熟,所以暴力的使用了JS來處理細節,并且我并沒有使用JQuery這些簡單的語法結構,沒有別的原因,我還不會~
三、原生調用
講了CSS和JS,接著應該講講怎么在原生中調用。
首先加載整個tab的html頁面
webView.loadUrl("file:///android_asset/radioGroup.html");
當然初始化數據是少不了的
/*將圖片地址和按鈕文案傳遞給h5頁面
*/private voidinitData() {
lists=newArrayList<>();
Item item =newItem();
item.setIcon_nor("http://192.168.111.20/drawable-hdpi/icon_tab_one_nor.png");
item.setIcon_sel("http://192.168.111.20/drawable-hdpi/icon_tab_one_sel.png");
//item.setIcon_sel("http://192.168.111.20/drawable-hdpi/push.png");item.setTitle("tab1");
Item item1 =newItem();
item1.setIcon_nor("http://192.168.111.20/drawable-hdpi/icon_tab_two_nor.png");
item1.setIcon_sel("http://192.168.111.20/drawable-hdpi/icon_tab_two_sel.png");
item1.setTitle("tab2");
Item item2 =newItem();
item2.setIcon_nor("http://192.168.111.20/drawable-hdpi/icon_tab_three_nor.png");
item2.setIcon_sel("http://192.168.111.20/drawable-hdpi/icon_tab_three_sel.png");
item2.setTitle("tab3");
Item item3 =newItem();
item3.setIcon_nor("http://192.168.111.20/drawable-hdpi/icon_tab_four_nor.png");
item3.setIcon_sel("http://192.168.111.20/drawable-hdpi/icon_tab_four_sel.png");
item3.setTitle("tab4");
lists.add(item);
lists.add(item1);
lists.add(item2);
lists.add(item3);
}
然后需要通過js將數據傳遞給html頁面
webView.setWebViewClient(newWebViewClient() {
@Override
public booleanshouldOverrideUrlLoading(WebView view, String url) {
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public voidonLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
}
@Override
public voidonPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
webView.loadUrl("javascript:tranData("+javaScriptInterface.getImages(lists) +")");
//webView.loadUrl("javascript:setChecked('four')");}
});
建議在onPageFinished這個方法里面調用js方法,這樣不容易出錯。
至于原生是怎么處理tab頁面的切換的,我就不講了,有興趣的小伙伴可以把源碼下載下來看一看,當然自己改改是最好的方式。
傳送門開啟:http://download.csdn.net/detail/zhimingshangyan/9648669
我的實現思路講完了,文章的開篇我提供了三種實現方式,第二和第三兩種方式我都實現了,當然第二種方式我沒有整理,歡迎小伙伴提供你們的思路給我,同時有什么問題和值得改進的地方小伙伴可以留言給我。