前言:对于一些社交达人,微信或者QQ斗图几乎家常便饭。然而许多人手机里却只有那么几个表情,这样怎么在表情大战中取胜呢?不过不要忘了,我们是程序猿,没有弹药库自己造呗!于是就有了这篇文章。
点此跳转到原文
项目github地址:https://github.com/VinceBarry/BiaoQingLib
功能展示
先看看最终效果(重点在功能,界面我就不作美化了,有兴趣的自己做一些美化)
这个app一共包含700+张金馆长表情,当我点击某张图片时,能够将图片发送到微信,QQ或其他第三方平台上;当长按图片时,能够将图片保存到特定的文件夹中;点击底部按钮时能够加载更多表情。这里只是功能实现讲解,如果学会了以后要多少弹药只是加几行代码的事。心动了吗?下面开始吧。
项目依赖
compile files('libs/jsoup-1.9.2.jar')
compile 'io.reactivex:rxjava:1.0.14'
compile 'io.reactivex:rxandroid:1.0.1'
compile 'com.squareup.picasso:picasso:2.5.2'
该项目需要用到上面几个库,分别是网络爬虫Jsoup,异步请求RxJava,RxAndroid和图片请求Picasso。对于每一个库的用法,RxJava可以翻阅我的上一篇博文,Picasso的用法我会在文中说明,相对简单;Jsoup参阅:http://www.open-open.com/jsoup/, 写的很详细。这几个库是该项目的基础,一旦会用就可以开始撸了。
具体实现
1.项目结构
如图所示,由于项目较简单,我就没有分包了:
2.界面布局
本项目只有一个Activity,我们只要在布局文件中添加一个GridView和底部一个Button即可。然后在Activity中实例化,这里就不多说了。
3.数据获取
表情的数据来源于 http://qq.yh31.com/zjbq/0551964.html ,由于我们需要使用爬虫爬取表情的图片地址,所以先查看网页的源码,发现我们需要的表情图片地址为:
[站外图片上传中……(4)]
于是就很明确了,我们先把img标签的部分过滤出来,但是img标签还有一部分图片不是我们所需的表情,如:[站外图片上传中……(5)]
。经过观察,发现不是我们所需的表情的img标签的class都为pic2,这样问题就解决了:
try {
Document doc = Jsoup.connect(URL)
.timeout(3000)
.get();
Elements elements = doc.getElementsByTag("img");
for (Element e : elements) {
if (!e.attr("class").toString().equals("pic2")) {
//TODO:
}
}
} catch (IOException e) {
e.printStackTrace();
}
上面的代码便是网络爬虫的部分。先过滤出img标签的内容,然后二次筛选,筛去class为“pic2”的部分。上面代码中的TODO部分便可以对我们已经筛选的表情的Element进行处理,到此我们数据抓取成功。
4.异步请求
获取到的表情图片的地址是String类型,下面我们通过RxJava来实现异步请求并将数据加载(这部分如果不熟悉RxJava的童鞋也可以用Handler或者AsyncTask等等异步请求,这里不多说了)
首先创建事件源(下面的步骤跟上一篇文章类似),我把代码贴出来:
public class RxBiaoQing {
private final static String URLPrefix = "http://qq.yh31.com";
public static Observable<Bitmap> getBiaoQing(final Context context, final String URL) {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
Document doc = Jsoup.connect(URL)
.timeout(3000)
.get();
Elements elements = doc.getElementsByTag("img");
for (Element e : elements) {
if (!e.attr("class").toString().equals("pic2")) {
subscriber.onNext(URLPrefix + e.attr("src").toString());
// L.i(URLPrefix+e.attr("src").toString());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).flatMap(new Func1<String, Observable<Bitmap>>() {
@Override
public Observable<Bitmap> call(String s) {
Bitmap bitmap = null;
try {
bitmap = Picasso.with(context).load(s).get();
} catch (IOException e) {
e.printStackTrace();
}
return Observable.just(bitmap);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
在事件源中进行网络表情爬取,每获得一个所需表情的图片地址时将其处理为Bitmap(这里使用Picasso库将String转化为Bitmap),注意flatmap将String变换为Bitmap的过程和调度器将事件源和观察者异步线程的过程。事件源完成后便是观察者的完成了。下面代码:
final Observer<Bitmap> observer = new Observer<Bitmap>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Bitmap bitmap) {
lruCache.set(position+"",bitmap);
L.i("size",lruCache.size()+"");
L.i("size","max:"+lruCache.size()+"");
L.i("size",position+"");
position++;
biaoQingAdapter.notifyDataSetChanged();
L.i(bitmap.toString());
}
};
上面便是观察者在接收到事件源发出的通知(即Bitmap对象)后将Bitmap写入LruCache缓存并通知GridView显示的过程。
LruCache缓存我使用的是Picasso库中的,也可以自己动手使用原生的LruCache,但本人感觉原生的不如Picasso方便。然后将事件源与观察者绑定:RxBiaoQing.getBiaoQing(MainActivity.this,URL).subscribe(observer);
上面完成了网络图片的异步请求和图片缓存的过程,下面便是将图片显示在GridView上。
5.数据展示
这一步最主要的是配置adapter。继承BaseAdapter,代码如下:
public class BiaoQingAdapter extends BaseAdapter {
private LayoutInflater layoutInflater;
private Context context;
private LruCache lruCache;
public BiaoQingAdapter(LruCache lruCache, Context context) {
layoutInflater = LayoutInflater.from(context);
this.lruCache = lruCache;
this.context = context;
}
@Override
public int getCount() {
return lruCache.size();
}
@Override
public Object getItem(int position) {
return lruCache.get(position+"");
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if(convertView == null){
viewHolder = new ViewHolder();
convertView = layoutInflater.inflate(R.layout.item_gridview,null);
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.item_iv);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
Bitmap bitmap = lruCache.get(position+"");
viewHolder.imageView.setImageBitmap(bitmap);
return convertView;
}
class ViewHolder{
ImageView imageView;
}
}
BaseAdapter的复写我就不多说了,主要要注意的是我将LruCache缓存传入适配器中,然后通过get()方法找寻到position对应的Bitmap,将Bitmap在对应的GridView的位置中显示出来。
6.图片下载分享
保存这个部分我就直接贴代码了。。。
public void saveToSD(int position){
String fileName = "biaoqing"+position +".jpg";
File appDir = new File(Environment.getExternalStorageDirectory(),"Tencent/MicroMsg/WeiXin");
if(!appDir.exists()){
Toast.makeText(context,"您未安装微信`.`!",Toast.LENGTH_SHORT).show();
appDir.mkdirs();
}
File file = new File(appDir,fileName);
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
lruCache.get(position+"").compress(Bitmap.CompressFormat.JPEG,100,fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
Toast.makeText(context,"弹药已经装到库中!",Toast.LENGTH_SHORT).show();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
注意上面的文件夹路径在SD根目录下的Tencent/MicroMsg/WeiXin中,你可以自己修改切换。此外分享部分就不多说了,可以通过直接调用微信SDK或mob的ShareSDK来实现发送。当然了,intent也可以试试。
7.添加权限
辛苦了半天还是不行?看看有没有添加权限吧~~博主马大哈总是忘记这个地方。。。还有Android6.0的权限问题也要注意喔!
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
总结
这个项目写下来还是很有价值的。第一,你获得了一个无穷的弹药库,以后斗图不再低人一等啦~~第二,你将收获RxJava,Picasso,Jsoup甚至BaseAdapter的使用技巧,这些都是很有价值的。。。由于博主水平不够,可能存在一些疏漏,欢迎评论指出。另外项目github地址在最上面,最好结合源码阅读此文。