1.背景大纲
距离乐逗项目到后天已经两周了,鉴于第一模块完成,用了这么长的时间,感觉真是惭愧!不过欣喜的是学习了不少关于 材料设计的知识,包括support 兼容包里的 一些布局,控件等 。当然 我最喜欢的还是 GitHub上的开源控件,有些可以使用,有些可以改编,哎,什么时候,才能自己做自己的自定义控件呢?好了,不说了,第一模块的完成,就代表着整个 App 已经完成了 50% 。因为 除了UI界面(其他模块肯定不能和这个一样啦)以外,其他的包括网络层,缓存,一些工具类,都已经实现过了。
在这里关于我的微信精选文章模块的实现,列以下大纲:
(1)UI
1)基础UI : FloatingActionButton
2)主UI : MaterialViewPager + Fragment (v4)
3)内容 : RecyclerView + CardView + material-ripple
4)进度条 : SwipeRefreshLayout (v4)
5)图标 : material-design-icons
6)提醒 : niftynotifation , SnackBar ,
7)圆型图片 :SelectableRoundedImageView
8)详情页 : WebView (X5)
(2)网络
1)数据请求: 官方SDK
2)图片请求: Universal-Image-Loader
(3)缓存
1)数据缓存 :ACache
2)图片缓存 : Universal-Image-Loader
(4)数据
1)随机图片 :http://lorempixel.com/
2)微信精选文章 :https://www.showapi.com
(5)工具
1)数据解析 : Gson
2)Android Studio 插件 : Gsonformat
(6)效果图演示
图 1 图2 图 3
2.UI
( 1)基础UI : FloatingActionButton
说明 : 因为在四个模块,均存在FloatingActionButton 和 个人中心按钮,总不能在四个模块中都实现浮动按钮,那样代码就显得累赘了。故实现 BaseActivity , 实现 个人中心按钮和 FloationActionButton.
(2)主UI : MaterialViewPager + Fragment (v4)
说明:这是开源控件,实现方法见 Github就可以实现,具体的操作文档,写的很详细。主界面的实现,因为分类很多,所以写了公共的Fragment , 用的是一个,所以每个分类下面 的界面排版都是一样的。
存在于Activity 中实现,而内容在Fragment中实现。
(3)内容 : RecyclerView + CardView + material-ripple
说明:内容页实现就是 Fragment 中实现加载内容 ,使用的RecyclerView,这里值得一提的是,使用RecyclerView可以对应 多个ViewHolder实现,这样可以设置不同的排版(见上图所示),不像是Listview一样,同一的排版;主要实现了ViewHolder的排版 和 实现Recycler.Adapter下面的方法 :
@Override
public int getItemViewType(int position) {
switch (position%7){
case 0:
return BIG_CARD;
default:
return SMALL_CARD;
}
}
实现:
下面是我的Fragment中 Recycler.viewHolder的整个 Adapter (子weismallviewholder不做展示):
基本思路:通过Type,返回不同的ViewHolder 对应的View即可。
说明 :weiBigCardViewHolder和weiSmallCardViewholder 都继承与Recycler.viewholder
package labelnet.cn.ledou.adpater;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
import labelnet.cn.ledou.R;
import labelnet.cn.ledou.event.WeiRecycleritemClick;
import labelnet.cn.ledou.model.WeiMsgModel.Showapi_res_bodyEntity.PagebeanEntity.ContentlistEntity;
import labelnet.cn.ledou.net.NetManager;
import labelnet.cn.ledou.net.NetManagerImp;
import labelnet.cn.ledou.ui.WeiBigCardViewHolder;
import labelnet.cn.ledou.ui.WeiSmallCardViewHolder;
import labelnet.cn.ledou.util.ImageLoaderUtil;
import labelnet.cn.ledou.util.TimeUtil;
/**
* 内容页实现 adapter
* 集成自开源项目 : MaterialViewPager
* 每个RecyclerViewFragment的Adapter
*/
public class WeiRecyclerViewBSAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<ContentlistEntity> contents;
private final int BIG_CARD=0;
private final int SMALL_CARD=1;
NetManager manager=new NetManagerImp();
public WeiRecyclerViewBSAdapter(List<ContentlistEntity> contents) {
this.contents = contents;
}
@Override
public int getItemViewType(int position) {
switch (position%7){
case 0:
return BIG_CARD;
default:
return SMALL_CARD;
}
}
@Override
public int getItemCount() {
return contents.size();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view=null;
switch(viewType){
case BIG_CARD:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_big_card, parent, false);
return new WeiBigCardViewHolder(view);
case SMALL_CARD:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_small_card, parent, false);
return new WeiSmallCardViewHolder(view);
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ContentlistEntity entity= contents.get(position);
String timeStr=TimeUtil.formatDisplayTime(entity.getDate(), "yyyy-MM-dd HH:mm");
switch (getItemViewType(position)){
case BIG_CARD:
WeiBigCardViewHolder weiBigCardViewHolder= (WeiBigCardViewHolder) holder;
weiBigCardViewHolder.tv_big_username.setText(entity.getUserName());
weiBigCardViewHolder.tv_big_date.setText(timeStr);
weiBigCardViewHolder.tv_big_title.setText(entity.getTitle());
weiBigCardViewHolder.tv_big_type.setText(entity.getTypeName());
manager.loadImage(entity.getContentImg(), weiBigCardViewHolder.iv_big_contentImg, ImageLoaderUtil.getContentImgOption());
manager.loadImage(entity.getUserLogo(),weiBigCardViewHolder.iv_big_userlogo);
break;
case SMALL_CARD:
WeiSmallCardViewHolder weiSmallCardViewHolder= (WeiSmallCardViewHolder) holder;
weiSmallCardViewHolder.tv_small_username.setText(entity.getUserName());
weiSmallCardViewHolder.tv_small_date.setText(timeStr);
weiSmallCardViewHolder.tv_small_title.setText(entity.getTitle());
manager.loadImage(entity.getContentImg(), weiSmallCardViewHolder.iv_small_contentImg,ImageLoaderUtil.getContentImgOption());
manager.loadImage(entity.getUserLogo(), weiSmallCardViewHolder.iv_small_userlogo);
break;
}
}
}
weiBigCardViewHolder 实现:
package labelnet.cn.ledou.ui;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.balysv.materialripple.MaterialRippleLayout;
import com.joooonho.SelectableRoundedImageView;
import labelnet.cn.ledou.R;
import labelnet.cn.ledou.event.WeiRecycleritemClick;
/**
* Created by yuan on 15-10-27.
*
* 微信文章的item ViewHolder操作
*
*/
public class WeiBigCardViewHolder extends RecyclerView.ViewHolder {
public MaterialRippleLayout ripple_big_wei;
public ImageView iv_big_contentImg;
public SelectableRoundedImageView iv_big_userlogo;
public TextView tv_big_title,tv_big_username,tv_big_date,tv_big_type;
public WeiRecycleritemClick itemClick;
public WeiBigCardViewHolder(View itemView,WeiRecycleritemClick itemClick) {
super(itemView);
this.itemClick=itemClick;
initView(itemView);
}
private void initView(View itemView) {
iv_big_contentImg= (ImageView) itemView.findViewById(R.id.iv_big_contentImg);
iv_big_userlogo= (SelectableRoundedImageView) itemView.findViewById(R.id.iv_big_userlogo);
tv_big_title= (TextView) itemView.findViewById(R.id.tv_big_title);
tv_big_username= (TextView) itemView.findViewById(R.id.tv_big_username);
tv_big_date= (TextView) itemView.findViewById(R.id.tv_big_date);
tv_big_type= (TextView) itemView.findViewById(R.id.tv_big_type);
ripple_big_wei= (MaterialRippleLayout) itemView.findViewById(R.id.ripple_big_wei);
ripple_big_wei.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
itemClick.onItemClick(v,getLayoutPosition());
}
});
}
}
对应的item布局实现 :
material-ripple :实现的是点击的波纹延伸效果 ,包裹在card中 ;
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="270dp"
android:layout_marginBottom="@dimen/cardMarginVertical"
android:layout_marginLeft="@dimen/cardMarginHorizontal"
android:layout_marginRight="@dimen/cardMarginHorizontal"
android:layout_marginTop="@dimen/cardMarginVertical"
app:cardCornerRadius="5dp"
app:cardElevation="2dp"
app:cardPreventCornerOverlap="false"
app:contentPadding="0dp">
<com.balysv.materialripple.MaterialRippleLayout
android:id="@+id/ripple_big_wei"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_big_contentImg"
android:layout_width="match_parent"
android:layout_height="200dp"
android:padding="5dp"
android:src="@drawable/moren" />
<TextView
android:id="@+id/tv_big_title"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:paddingLeft="5dp"
android:paddingTop="3dp"
android:text="杨幂关注胡歌是小事,胡歌的后宫和谐才是大事!"
android:textColor="@color/black_overlay" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:paddingLeft="5dp"
android:paddingTop="3dp">
<com.joooonho.SelectableRoundedImageView xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/iv_big_userlogo"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:src="@drawable/logo"
app:sriv_border_color="#eee"
app:sriv_border_width="2dip"
app:sriv_left_bottom_corner_radius="48dip"
app:sriv_left_top_corner_radius="16dip"
app:sriv_oval="true"
app:sriv_right_bottom_corner_radius="16dip"
app:sriv_right_top_corner_radius="0dip" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:text=" | "
android:textColor="@color/half_black" />
<TextView
android:id="@+id/tv_big_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:text="巴拉莎莎"
android:textColor="@color/fu_color"
android:textSize="10sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center|right"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_big_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|right"
android:drawablePadding="3dp"
android:drawableRight="@drawable/ic_loyalty_grey_600_18dp"
android:gravity="right"
android:paddingRight="10dp"
android:text="端子手"
android:textColor="@color/half_black"
android:textSize="10sp" />
<TextView
android:id="@+id/tv_big_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="3dp"
android:drawableRight="@drawable/ic_access_time_grey_600_18dp"
android:layout_gravity="center|right"
android:paddingRight="10dp"
android:text="19分钟前"
android:textColor="@color/half_black"
android:textSize="10sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</com.balysv.materialripple.MaterialRippleLayout>
</android.support.v7.widget.CardView>
</FrameLayout>
RecyclerView item点击事件实现 :
思路:RecyclerView本身没有item监听事件,实现是在viewhodler实现,给viewhodler添加点击事件,通过接口回调来实现。这里就不实现了,见WeiBigCardViewHolder。
问题:如果使用Material Ripple 的点击效果的话,那么如果给ViewHolder添加点击事件是不管用的,解决方法也很简单,给Material Ripple 添加点击事件 就可以了。
(4)进度条 : SwipeRefreshLayout (v4)
说明:进度条的实现,在Fragment中实现,而不能在Viewpager中实现,毕竟刷新的时候,是刷新单个Fragment,而不是整个Viewpager.
布局实现:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/wei_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swiperefresh_wei"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
设置实现 :
/**
* 3.进度条实现
*/
swiperefresh_wei.setColorSchemeResources(R.color.zhu_color, R.color.zhushen_color
, R.color.fu_color, R.color.black_overlay);
swiperefresh_wei.setSize(SwipeRefreshLayout.LARGE);
//设置进度条的位置
swiperefresh_wei.setProgressViewEndTarget(true, 800);
//设置监听
swiperefresh_wei.setOnRefreshListener(swifRefushListener);
监听事件实现:
/**
* 1.下拉刷新监听
* 下拉刷新
*/
class SwifRefushListener implements SwipeRefreshLayout.OnRefreshListener {
@Override
public void onRefresh() {
mIsRefreshing = true;
}
}
一开始就启动进度条实现:
/**
* 进度条实现
*/
private void swifRefresh() {
//设置启动后就进行刷新
if (String.valueOf(0).equals(typeId)) {
swiperefresh_wei.post(new Runnable() {
@Override
public void run() {
swiperefresh_wei.setRefreshing(true);
mIsRefreshing = true;
}
});
swifRefushListener.onRefresh();
}
}
(5)图标 : material-design-icons
说明 : Google公司提供了material-design-icons图表,使用十分方便。
使用 : 在Android Studio 中安装插件 Material design icon 插件,使用方法自己玩玩就会了。
(6)提醒 : niftynotifation , SnackBar
说明 :nifynotification 效果是使用在进行一些通知效果,我这里代替了toast .
SnackBar 的使用是在没网的情况下,来通知设置网络。
问题:改变nifynotigication 的显示位置 ,解决:
(1) 为了让 个性通知绑定 Viewgroup而实现;(个性通知 niftynotification );
(2) 使用 niftynotifition 时,需要绑定一个viewGroup , 故控件是不可以的;如果这个viewGroup 全屏,那么通知就在顶部显示了;
(3)故,如果将viewgroup ,在底部,那么就在底部显示了,使用 LinearLayout+frameLayout ,恰好合适;
(7)圆型图片 :SelectableRoundedImageView
说明 :实现见 Github,使用仅仅布局就可以实现;
(8)详情页 : WebView (X5)
说明:在app中加载网页,自己尝试了很多方法,使用webview实现。但是都达不到预期像微信中的一样,经过观察微信的发现是QQ浏览器提供的支持,后就查了些资料,终于找到了腾讯的SDK (隐藏的够深),解决了纠结不能再纠结的问题,话说,真的很强大哦。
官方地址:http://x5.tencent.com/index
实现:虽然官方给出来了,但是,没有进行封装,还是有很大的扩展性。如果想要自定义的话,可以看看官方的demo。简单的打开链接的话,就看看下面的实现就可以了。
首先,下载官方的SDK (一个jar包).
自定义webview实现:
package labelnet.cn.ledou.ui;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.tencent.smtt.export.external.interfaces.IX5WebChromeClient.CustomViewCallback;
import com.tencent.smtt.export.external.interfaces.JsPromptResult;
import com.tencent.smtt.export.external.interfaces.JsResult;
import com.tencent.smtt.export.external.interfaces.WebResourceResponse;
import com.tencent.smtt.sdk.ValueCallback;
import com.tencent.smtt.sdk.WebChromeClient;
import com.tencent.smtt.sdk.WebSettings;
import com.tencent.smtt.sdk.WebSettings.LayoutAlgorithm;
import com.tencent.smtt.sdk.WebStorage;
import com.tencent.smtt.sdk.WebView;
import com.tencent.smtt.sdk.WebViewClient;
import java.util.HashMap;
import java.util.Map;
import labelnet.cn.ledou.R;
public class X5WebView extends WebView {
///
//add private object
//
//file chooser result code
public static final int FILE_CHOOSER = 0;
private String resourceUrl = "";
private WebView smallWebView;
private static boolean isSmallWebViewDisplayed = false;
private Map<String, Object> mJsBridges;
@SuppressLint("SetJavaScriptEnabled")
public X5WebView(Context arg0, AttributeSet arg1) {
super(arg0, arg1);
WebStorage webStorage = WebStorage.getInstance();
// TODO Auto-generated constructor stub
WebSettings webSetting = this.getSettings();
webSetting.setJavaScriptEnabled(true);
webSetting.setJavaScriptCanOpenWindowsAutomatically(true);
webSetting.setAllowFileAccess(true);
webSetting.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS);
webSetting.setSupportZoom(true);
webSetting.setBuiltInZoomControls(true);
webSetting.setUseWideViewPort(true);
webSetting.setSupportMultipleWindows(true);
webSetting.setLoadWithOverviewMode(true);
webSetting.setAppCacheEnabled(true);
webSetting.setDatabaseEnabled(true);
webSetting.setDomStorageEnabled(true);
webSetting.setGeolocationEnabled(true);
webSetting.setAppCacheMaxSize(Long.MAX_VALUE);
// webSetting.setPageCacheCapacity(IX5WebSettings.DEFAULT_CACHE_CAPACITY);
webSetting.setPluginState(WebSettings.PluginState.ON_DEMAND);
webSetting.setRenderPriority(WebSettings.RenderPriority.HIGH);
webSetting.setCacheMode(WebSettings.LOAD_NO_CACHE);
this.getView().setClickable(true);
this.getView().setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return false;
}
});
//WebClient settings
this.setWebViewClient(new WebViewClient() {
/**
* 防止加载网页时调起系统浏览器
*/
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
// return false;
}
public void onReceivedHttpAuthRequest(WebView webview, com.tencent.smtt.export.external.interfaces.HttpAuthHandler httpAuthHandlerhost, String host, String realm) {
boolean flag = httpAuthHandlerhost.useHttpAuthUsernamePassword();
Log.i("yuanhaizhou", "useHttpAuthUsernamePassword is" + flag);
Log.i("yuanhaizhou", "HttpAuth host is" + host);
Log.i("yuanhaizhou", "HttpAuth realm is" + realm);
}
@Override
public void onDetectedBlankScreen(String arg0, int arg1) {
// TODO Auto-generated method stub
super.onDetectedBlankScreen(arg0, arg1);
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView arg0,
String arg1) {
// TODO Auto-generated method stub
return super.shouldInterceptRequest(arg0, arg1);
}
});
//webchromeclient settings
this.setWebChromeClient(new WebChromeClient() {
View myVideoView;
View myNormalView;
CustomViewCallback callback;
///
//
/**
* 全屏播放配置
*/
@Override
public void onShowCustomView(View view, CustomViewCallback customViewCallback) {
// TODO Auto-generated method stub
FrameLayout normalView = (FrameLayout) ((Activity) getContext()).findViewById(R.id.web_filechooser);
ViewGroup viewGroup = (ViewGroup) normalView.getParent();
viewGroup.removeView(normalView);
viewGroup.addView(view);
myVideoView = view;
myNormalView = normalView;
callback = customViewCallback;
}
@Override
public void onHideCustomView() {
// TODO Auto-generated method stub
if (callback != null) {
callback.onCustomViewHidden();
callback = null;
}
if (myVideoView != null) {
ViewGroup viewGroup = (ViewGroup) myVideoView.getParent();
viewGroup.removeView(myVideoView);
viewGroup.addView(myNormalView);
}
}
@Override
public void onProgressChanged(WebView arg0, int arg1) {
// TODO Auto-generated method stub
super.onProgressChanged(arg0, arg1);
}
@Override
public void openFileChooser(ValueCallback<Uri> uploadFile,
String acceptType, String captureType) {
Log.i("ChromeClient", "openFileChooser enter");
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
((Activity) (X5WebView.this.getContext())).startActivityForResult(
Intent.createChooser(
i, "choose files"), X5WebView.FILE_CHOOSER);
super.openFileChooser(uploadFile, acceptType, captureType);
}
@Override
public void onShowCustomView(View arg0, int arg1,
CustomViewCallback arg2) {
// TODO Auto-generated method stub
CustomViewCallback callback = new CustomViewCallback() {
@Override
public void onCustomViewHidden() {
// TODO Auto-generated method stub
Log.i("yuanhaizhou", "video view hidden");
}
};
super.onShowCustomView(arg0, arg1, arg2);
}
/**
* webview 的窗口转移
*/
@Override
public boolean onCreateWindow(WebView arg0, boolean arg1,
boolean arg2, Message msg) {
// TODO Auto-generated method stub
Log.i("yuanhaihzou", "onCreateWindow happend!!");
if (X5WebView.isSmallWebViewDisplayed == true) {
WebView.WebViewTransport webViewTransport = (WebView.WebViewTransport) msg.obj;
WebView webView = new WebView(X5WebView.this.getContext()) {
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.GREEN);
paint.setTextSize(15);
canvas.drawText("新建窗口", 10, 10, paint);
}
;
};
webView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView arg0, String arg1) {
arg0.loadUrl(arg1);
return true;
}
;
});
FrameLayout.LayoutParams lp = new LayoutParams(400, 600);
lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL;
X5WebView.this.addView(webView, lp);
webViewTransport.setWebView(webView);
msg.sendToTarget();
}
return true;
}
@Override
public boolean onJsAlert(WebView arg0, String arg1, String arg2,
JsResult arg3) {
// TODO Auto-generated method stub
AlertDialog.Builder builder = new Builder(getContext());
builder.setTitle("X5内核");
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
dialog.dismiss();
}
});
builder.show();
arg3.confirm();
return true;
}
/**
*对应js 的通知弹框 ,可以用来实现js 和 android之间的通信
*/
@Override
public boolean onJsPrompt(WebView arg0, String arg1, String arg2,
String arg3, JsPromptResult arg4) {
// TODO Auto-generated method stub
//在这里可以判定js传过来的数据,用于调起android native 方法
if (X5WebView.this.isMsgPrompt(arg1)) {
if (X5WebView.this.onJsPrompt(arg2, arg3)) {
return true;
} else {
return false;
}
}
return super.onJsPrompt(arg0, arg1, arg2, arg3, arg4);
}
@Override
public void onReceivedTitle(WebView arg0, final String arg1) {
// TODO Auto-generated method stub
super.onReceivedTitle(arg0, arg1);
Log.i("yuanhaizhou", "webpage title is " + arg1);
}
});
}
/
//绘制webview的标记
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
boolean ret = super.drawChild(canvas, child, drawingTime);
canvas.save();
Paint paint = new Paint();
paint.setColor(0x7fff0000);
paint.setTextSize(24.f);
paint.setAntiAlias(true);
/**
* 取消绘制的标识
* 包括内核
* 手机信息
* 手机名称
*/
// if (getX5WebViewExtension() != null) {
// //Log.d(TAG, "drawChild--X5 Core");
// canvas.drawText(this.getContext().getPackageName() + "-pid:" + android.os.Process.myPid(), 10, 50, paint);
// canvas.drawText("X5 Core:" + QbSdk.getTbsVersion(this.getContext()), 10, 100, paint);
// } else {
// //Log.d(TAG, "drawChild--Sys Core");
// canvas.drawText(this.getContext().getPackageName() + "-pid:" + android.os.Process.myPid(), 10, 50, paint);
// canvas.drawText("Sys Core", 10, 100, paint);
// }
// canvas.drawText(Build.MANUFACTURER, 10, 150, paint);
// canvas.drawText(Build.MODEL, 10, 200, paint);
canvas.restore();
return ret;
}
public X5WebView(Context arg0) {
super(arg0);
setBackgroundColor(85621);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
return super.onInterceptTouchEvent(ev);
}
public static void setSmallWebViewEnabled(boolean enabled) {
isSmallWebViewDisplayed = enabled;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
// TODO Auto-generated method stub
// Log.i("yuanhaizhou","webview scroll y is" +this.getView().getScrollY());
// Log.i("yuanhaizhou","real webview scroll y is" + this.getScrollY());
// Log.i("yuanhaizhou","webview webscroll y is" + this.getWebScrollY());
Log.i("yuanhaizhou", "webview webscroll y is" + this.getWebScrollY());
super.onScrollChanged(l, t, oldl, oldt);
}
public void addJavascriptBridge(SecurityJsBridgeBundle jsBridgeBundle) {
if (this.mJsBridges == null) {
this.mJsBridges = new HashMap<String, Object>(5);
}
if (jsBridgeBundle != null) {
String tag = SecurityJsBridgeBundle.BLOCK + jsBridgeBundle.getJsBlockName()
+ "-"
+ SecurityJsBridgeBundle.METHOD + jsBridgeBundle.getMethodName();
this.mJsBridges.put(tag, jsBridgeBundle);
}
}
/**
* 当webchromeClient收到 web的prompt请求后进行拦截判断,用于调起本地android方法
*
* @param methodName 方法名称
* @param blockName 区块名称
* @return true :调用成功 ; false :调用失败
*/
private boolean onJsPrompt(String methodName, String blockName) {
String tag = SecurityJsBridgeBundle.BLOCK + blockName
+ "-"
+ SecurityJsBridgeBundle.METHOD + methodName;
if (this.mJsBridges != null && this.mJsBridges.containsKey(tag)) {
((SecurityJsBridgeBundle) this.mJsBridges.get(tag)).onCallMethod();
return true;
} else {
return false;
}
}
/**
* 判定当前的prompt消息是否为用于调用native方法的消息
*
* @param msg 消息名称
* @return true 属于prompt消息方法的调用
*/
private boolean isMsgPrompt(String msg) {
if (msg != null && msg.startsWith(SecurityJsBridgeBundle.PROMPT_START_OFFSET)) {
return true;
} else {
return false;
}
}
}
帮助类实现 :
package labelnet.cn.ledou.ui;
import android.content.Context;
import java.util.Map;
/**
* X5WebView , 应用类 , 提供JSBridge
*/
public abstract class SecurityJsBridgeBundle {
///
//add js
private final static String DEFAULT_JS_BRIDGE ="JsBridge";
public static final String METHOD = "method";
public static final String BLOCK = "block";
public static final String CALLBACK = "callback";
public static final String PROMPT_START_OFFSET = "local_js_bridge::";
private Context mContext;
private String mJsBlockName ;
private String mMethodName;
public abstract void onCallMethod();
public SecurityJsBridgeBundle(String JsBlockName, String methodName) throws Exception{
if(methodName == null){
throw new Exception("methodName can not be null!");
}
if(JsBlockName!=null){
this.mJsBlockName=JsBlockName;
}else{
this.mJsBlockName = DEFAULT_JS_BRIDGE;
}
}
public String getMethodName(){
return this.mMethodName;
}
public String getJsBlockName(){
return this.mJsBlockName;
}
private void injectJsMsgPipecode(Map<String,Object> data){
if(data==null){
return ;
}
String injectCode = "javascript:(function JsAddJavascriptInterface_(){ "+
"if (typeof(window.jsInterface)!='undefined') {"+
"console.log('window.jsInterface_js_interface_name is exist!!');} "+
"else {"+
data.get(BLOCK)+data.get(METHOD)+
"window.jsBridge = {"+
"onButtonClick:function(arg0) {"+
"return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]}));"+
"},"+
"onImageClick:function(arg0,arg1,arg2) {"+
"prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',args:[arg0,arg1,arg2]}));"+
"},"+
"};"+
"}"+
"}"+
")()";
}
private static String getStandardMethodSignature(){
return null;
}
}
开始使用 - 布局实现:
<labelnet.cn.ledou.ui.X5WebView
android:id="@+id/web_filechooser"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1" />
开始使用 - 业务实现 :
package labelnet.cn.ledou;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import labelnet.cn.ledou.model.WeiMsgModel;
import labelnet.cn.ledou.ui.X5WebView;
import labelnet.cn.ledou.util.GsonUtil;
/**
* Created by yuan on 15-10-31.
*
* 腾讯x5 内嵌浏览器使用
* webview使用
*
*/
public class TbWebViewActivity extends AppCompatActivity{
private X5WebView x5WebView;
private TextView tv_text;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tbwebview);
x5WebView= (X5WebView) findViewById(R.id.web_filechooser);
tv_text= (TextView) findViewById(R.id.tv_text);
x5WebView.setSmallWebViewEnabled(true);
//获得item数据
String json=getIntent().getStringExtra("weiitem");
initView(json);
}
private void initView(String json) {
WeiMsgModel.Showapi_res_bodyEntity.PagebeanEntity.ContentlistEntity entity= GsonUtil.getGsonUtilInstance().getWeiModelByJson(json);
x5WebView.loadUrl(entity.getUrl());
tv_text.setText(entity.getTitle());
}
@Override
public void onBackPressed() {
x5WebView.destroyDrawingCache();
x5WebView.destroy();
finish();
}
}
3.网络
说明: 图片加载及其缓存实现文章 :http://blog.csdn.net/lablenet/article/details/49465233 ,数据的加载 必须使用官方提供的SDK, 自己的因为缺乏参数,请求不到数据。
4.缓存
说明: 实现方法见 Github , 用法超级简单 , put , get 操作。
5.工具
说明 :Gson 提供了强大的JSON解析工作,直接将 JSON字符串 转化为 对象 ,使用十分方便。
当然Android Studio 的插件,在使用的时候,也是必不可少的。
6.一些问题
问题1:上篇已经有了这里问题 ,解决方法如下: 如注释所示,完美的解决方案。
/**
* 暂时没有使用
* 本意:解决下面问题呢 ,现在没必要了;
* (1)有网:在RecyclerView中,当在进行进度条加载的时候,滑动 会触发异常;
* (2)没网:滑动 会触发异常;
* java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{4320e810 position=3 id=-1, oldPos=-1, pLpos:-1 no parent}
* <p/>
* 解决:
* 在进行数据请求的时候,也就是 下拉刷新的时候,进行滑动Recycler , 但是不要清除 item,
* 如果提前清除了,那么就发生以上的错误;
* 故在刷新时,当数据过来,准备进行适配的时候,在进行 清除item 就可以了;
* 这也许是最完美的解决了吧
*/
private class mRecyclerViewOnTouchListener implements RecyclerView.OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mIsRefreshing) {
mIsRefreshing = false;
return mIsRefreshing;
} else {
return true;
}
}
}
问题2 : 使用ViewPager ,同时有多个Fragment ,这时候如果先加载进度条的话,那么这些Fragment一开始就会去请求数据,并填充内容,这样的话,不仅浪费流量,加载速度也慢。这个问题,纠结了2天。
解决 :
声明:我的共有 19个分类 ,也就是 19个 Viewpager 页面。
(1)在加载第一个Fragment的时候,进行进度条加载并提醒,其他的不进行首次进度条加载数据。
(2)其他的在点击或滑动到那个Fragment的时候,就行加载,当然没有进度条但是提醒,当然需要判断是不是第一个Fragment ,如果是第一个不进行加载,进度条会进行加载。下面代码进行首次进入时,进行数据加载实现:重写setUserVisibleHint方法,为true的时候,进行首次加载数据。
/**
* onResum时,进行加载
* 这个 不进行了
*
* @param isVisibleToUser
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if (isVisibleToUser) {
if (!String.valueOf(0).equals(typeId) && once_loaddata) {
mIsRefreshing = true;
//进行数据加载
onePageAccess(1);
}
}
}
(3)试了很多方法,只有这种是最优的方案。其实就是在启动的时候,有两个进行首次加载的方式,一种是setUserVisibleHint中进行加载;另一种是启动进度条进行加载;怎么配合,可以更为用户省流量,那个就是最优的。
(4)我的是 第一个页面使用进度条进行数据加载 ;其余页面进行 setUserVisibleHint中进行加载。这样做的话,第一个页面也就是引入用户眼帘的界面,故有进度条+提醒 ,比较友好;其他页面在首次加载的话,是后台加载的,没有进度条显示,但是有提醒。
7.总结
总体思路:ubuntu没有visio , 没办法 word 将就下吧。当然其中的细节,这里就不做了。
优化:
包括上面的问题2都是 ,就行数据加载的优化为剩流量考虑。图标等细节优化。
下周进行的内容就开始 第二模块的制作,当然先会进行 Ui模块的制作,下面如果顺利的话,进行节奏的就快了。