RxJava+Volley实现图片可点击的TextView

来源:互联网 发布:windows vista 真漂亮 编辑:程序博客网 时间:2024/05/17 09:43

前言

RxJava是一个实现java响应式编程的库。个人觉得它的优势是能够简化异步处理的逻辑,从而使整个业务流程能够更好的被读者理解,在处理异步业务的时候有处理同步业务的类似体验。个人理解可能不是很准确,对RxJava理解较浅的可以看看扔物线大牛的牛文给 Android 开发者的 RxJava 详解。

Volley这个库作为Android开发者肯定是知道的,是Google开源的一个网络框架,适用于数据小网络请求频繁的场景。

阅读本文前你应该先了解过RxJava和Volley。

实现思路

我这里需要解决的问题是使用TextView加载一段HTML,HTML中有部分是图片,需要能够点击进入一个可缩放图片的页面查看图片的细节。
TextView加载HTML很简单textView.setText(Html.fromHtml(htmlString))即可。加载HTML中的图片也很简单,只要实现一个ImageGetter,然后在Html.fromHtml(htmlString, imageGetter, tagHandler)使用即可。而实现TextView中部分内容的点击使用ClickableSapn可以实现。

解决问题的难点是,HTML中的图片在加载HTML的时候需要先从网络下载,我们需要在所有图片下载完成后再去刷新TextView,达到显示图片的目的。之前使用Volley的时候都是使用CallBack<T>的方式,这样在处理单个异步任务的时候还没有问题,但是在处理一组任务的时候就有些不足。之前RxJava就是一个很火很先进的库,自己也进行过了解,知道使用RxJava在处理一组异步任务的时候相当方便,所以就想尝试一下能否使用RxJava和Volley来解决遇到的问题。功夫不负有心人,在查过不少资料之后,经过尝试终于找到了一个可行的方案。

Volley除了提供Response.Listener<T>,和Response.ErrorListenerCallBack<T>方式,还提供了RequestFuture<T>的方式。这样我们就可以实现同步方式处理异步的请求。
举个栗子:

private RequestQueue requestQueue = Volley.newRequestQueue(context);// response listenerpublic void getImageFromNet(String url, int maxWidth, int maxHeight, Bitmap.Config config, Response.Listener<Bitmap> listener,    Response.ErrorListener errorListener) {  ImageRequest request = new ImageRequest(url, listener, maxWidth, maxHeight, config, errorListener);  request.setShouldCache(true);  request.setTag(url);  requestQueue.add(request);}// request futurepublic Bitmap getImageFromNet(String url, int maxWidth, int maxHeight, Bitmap.Config config)    throws ExecutionException, InterruptedException {  RequestFuture<Bitmap> requestFuture = RequestFuture.newFuture();  ImageRequest request = new ImageRequest(url, requestFuture, maxWidth, maxHeight, config, requestFuture);  request.setShouldCache(true);  request.setTag(url);  requestQueue.add(request);  return requestFuture.get();}

设计思路:
- 因为使用了图片的缓存,所以在textView.setText(spanned)中先显示已经缓存过的部分图片。
- 收集需要下载的图片地址之后,使用RxJava和Volley下载所有图片。
- 再次textView.setText(spanned),刷新TextView。

主要代码:

private OnImageClickListener listener;private CompositeSubscription compositeSubscription;public void setHtmlString(String htmlString) {  // 要响应点击,这句必须  setMovementMethod(LinkMovementMethod.getInstance());  this.htmlString = htmlString;  Spanned spanned = Html.fromHtml(htmlString, VolleyImageGetter.from(this), null);  // 先显示文字和部分图片  setText(spanned);  // 获取需要下载的图片url  SpannableStringBuilder spannableStringBuilder;  if (spanned instanceof SpannableStringBuilder) {    spannableStringBuilder = (SpannableStringBuilder) spanned;  } else {    spannableStringBuilder = new SpannableStringBuilder(spanned);  }  ImageSpan[] imageSpans =      spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), ImageSpan.class);  if (imageSpans != null && imageSpans.length > 0) {    List<String> sources = new ArrayList<>();    for (ImageSpan span : imageSpans) {      final String url = span.getSource();      if (!AndroidHelper.getImageDiskCache().hasBitMap(VolleyHelper.getCacheKey(url))) {        LogHelper.e("add url:" + url);        sources.add(url);      }    }    if (sources.size() > 0) {      getImageInRxWay(sources);    } else {      // 如果不需要下载图片则处理图片的点击      resetTextAndClickable();    }  }}private void resetTextAndClickable() {  Spanned spanned =      Html.fromHtml(htmlString, VolleyImageGetter.from(ImageClickableTextView.this), null);  SpannableStringBuilder spannableStringBuilder;  if (spanned instanceof SpannableStringBuilder) {    spannableStringBuilder = (SpannableStringBuilder) spanned;  } else {    spannableStringBuilder = new SpannableStringBuilder(spanned);  }  ImageSpan[] imageSpans =      spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), ImageSpan.class);  if (imageSpans != null && imageSpans.length > 0) {    for (ImageSpan span : imageSpans) {      final String url = span.getSource();      int start = spannableStringBuilder.getSpanStart(span);      int end = spannableStringBuilder.getSpanEnd(span);      ClickableSpan clickableSpan = new ClickableSpan() {        @Override public void onClick(View widget) {          if (listener != null) {            listener.imageClicked(url);          }        }      };      // remove other clickable span      ClickableSpan[] clickableSpans =          spannableStringBuilder.getSpans(start, end, ClickableSpan.class);      if (clickableSpans != null && clickableSpans.length > 0) {        for (ClickableSpan span1 : clickableSpans) {          spannableStringBuilder.removeSpan(span1);        }      }      // add image clickable span      spannableStringBuilder.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);    }  }  setText(spanned);}// get image from net in rxjava wayprivate void getImageInRxWay(List<String> sources) {  if (compositeSubscription == null) {    compositeSubscription = new CompositeSubscription();  }  Subscription subscription =      Observable.from(sources)          .flatMap(new Func1<String, Observable<BitmapEntry>>() {            @Override public Observable<BitmapEntry> call(String s) {              return getBitmapEntry(s);            }          })          .subscribeOn(Schedulers.io())          .observeOn(AndroidSchedulers.mainThread())          .subscribe(new Subscriber<BitmapEntry>() {            @Override public void onCompleted() {              LogHelper.e("onCompleted");              resetTextAndClickable();            }            @Override public void onError(Throwable e) {              if (e instanceof VolleyError) {                 VolleyError cause = (VolleyError) e.getCause();                 String s = new String(cause.networkResponse.data, Charset.forName("UTF-8"));                 LogHelper.e(s);              } else {                 LogHelper.e(e.getMessage());              }            }            @Override public void onNext(BitmapEntry entry) {              // add bitmap to cache              if (entry.getBitmap() != null) {                AndroidHelper.getImageDiskCache().putBitmap(entry.getKey(), entry.getBitmap());              }            }          });  compositeSubscription.add(subscription);}// 使用defer来提高性能// defer操作符是直到有订阅者订阅时,才通过Observable的工厂方法创建Observable并执行,// 在使用rxjava中我们应该使用defer来包装慢的操作private Observable<BitmapEntry> getBitmapEntry(final String url) {  return Observable.defer(new Func0<Observable<BitmapEntry>>() {    @Override public Observable<BitmapEntry> call() {      try {        return Observable.just(getImageBitmap(url));      } catch (ExecutionException | InterruptedException e) {        LogHelper.e(e.getMessage());        return Observable.error(e);      }    }  });}private BitmapEntry getImageBitmap(String url) throws ExecutionException, InterruptedException {  RequestFuture<Bitmap> future = RequestFuture.newFuture();  ImageRequest request = new ImageRequest(url, future, 0, 0, Bitmap.Config.RGB_565, future);  VolleyHelper.addRequest2Queue(request);  String key = VolleyHelper.getCacheKey(url);  Bitmap bitmap = future.get();  return new BitmapEntry(key, bitmap);}public interface OnImageClickListener {  public void imageClicked(String imageUrl);}

完整的代码:https://github.com/Zhaoyy/ikanxue/blob/2.x/app/src/main/java/com/mislead/ikanxue/app/view/ImageClickableTextView.java

个人对于RxJava的理解也是比较浅,相信多看多练也能掌握这个新思想。

参考

  • http://stackoverflow.com/questions/32701331/rxjava-and-volley-requests
  • https://github.com/zzhoujay/RichText/blob/master/app/src/main/java/zhou/widget/RichText.java
0 0
原创粉丝点击