【worktile开发小记】TextView中Markdown的显示

【worktile开发小记】TextView中Markdown的显示

1.引言

在worktile中,无论是免费版还是企业版,在显示任务、日程、文档、评论等等这些的描述的时候,都可以支持markdown,然而一般在java中的markdown解析,可能会有很多人用markdown4j这个第三方库,但是我没有使用这个库,而是使用了Bypass这个库。这是一个用C语言编写的Markdown解析第三方库,声称速度是Markdown4j此类使用Html的库的4倍。

2.Bypass

注意: 使用Bypass需要会使用NDK

1.1 编译

因为这个第三方库是用C语言编写的,所以在Android上使用的时候,需要编译好的so包,幸运的是,这个库已经提供好了Android.mk文件,所以我们可以很方便地编译Bypass库。

不过,这个库需要boost的支持,需要boost安装在系统上。详情请见:安装boost

1.2 java和jni

除了C语言库之外,Bypass还需要用jni来调用C语言函数,用SpannableString来负责在Android TextView显示不同的样子,所以还需要一个java library。

一般来说,在Android中引入java library,只需在gradle dependencies加入就好,可是在我这里总是有错误(我不清楚原因是什么,也许可能是因为我使用gradkle-experimental的原因),所以我就把Bypass的源代码复制到了工程中(同时我也修改了部分源代码),但是要注意路径,因为jni的函数名是Java_in_uncod_android_bypass开头的。

我的目录结构如下:
Bypass在我的工程中的位置

1.3 How-To

参考github:Building and Using

3.ImgeGetter

如果Markdown中包含图片,需要使用IamgeSpan将占位符变为图片,这时候需要使用ImageGetter来从内存、文件、网络等等来获取并处理图片。以下贴出我的ImageGetter代码(以下代码有一部分是很久前我在网上看到的,原作者已经不记得了,如果原作者看到并认为我侵权,我会立即从博客上删除或者修改代码)

public class Markdown {

    public static class MarkdownImageGetter implements Bypass.ImageGetter {
        private String cacheDirPath;

        Context context;
        TextView textView;

        public MarkdownImageGetter(Context context, TextView tv) {
            this.context = context;
            this.textView = tv;
            cacheDirPath = context.getApplicationContext().getCacheDir().getPath() + "/image/";
        }

        @Override
        public Drawable getDrawable(String source) {

            // 1、取缓存
            if (LCApplication.mTaskDetailImageMap.containsKey(source)) {
                SoftReference<Bitmap> cacheBitmap = LCApplication.mTaskDetailImageMap
                        .get(source);
                Bitmap softBitmap = null;
                if (cacheBitmap != null) {
                    softBitmap = cacheBitmap.get();
                    Drawable softDrawable = new BitmapDrawable(softBitmap);
                    softDrawable.setBounds(getDefaultBitmapRounds(softDrawable, context));

                    return softDrawable;
                }
            }

            // 2、取文件
            String fileName = source.substring(source.lastIndexOf("/") + 1);
            File file = new File(cacheDirPath + fileName);
            if (file.exists()) {

                Bitmap fileBitmap = BitmapUtils.createImageThumbnail(cacheDirPath + fileName);
                Drawable fileDrawable = new BitmapDrawable(fileBitmap);
                fileDrawable.setBounds(getDefaultBitmapRounds(fileDrawable, context));

                return fileDrawable;
            }

            // 3、取网络请求
            URLDrawable uRLDrawable = new URLDrawable();
            LoadImageAsync loadBitmap = new LoadImageAsync(uRLDrawable);
            loadBitmap.execute(source, fileName);
            return uRLDrawable;
        }

        /**
         * 图片保存到本地
         *
         */
        public void saveBitmap2File(InputStream in, String dirPath, String fileName)
                throws IOException {

            if (in == null) {
                return;
            }

            if (dirPath == null || dirPath.equals("")) {
                return;
            }

            if (fileName == null || fileName.equals("")) {
                return;
            }

            if (!Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                return;
            }

            File dir = new File(dirPath);
            if (!dir.exists()) {
                dir.mkdirs();
            }

            File file = new File(dirPath + fileName);
            if (!file.exists()) {
                file.createNewFile();
            }

            OutputStream out = new FileOutputStream(file);
            byte[] buffer = new byte[1024];
            int length = -1;

            while ((length = in.read(buffer)) != -1) {
                out.write(buffer, 0, length);
            }

            out.flush();
            out.close();
        }
        //网络下载图片
        public class LoadImageAsync extends AsyncTask<String, Void, Drawable> {

            URLDrawable mUrlDrawable;

            public LoadImageAsync(URLDrawable mUrlDrawable) {
                this.mUrlDrawable = mUrlDrawable;
            }

            @Override
            protected Drawable doInBackground(String... arg0) {

                String url = arg0[0];
                String fileName = arg0[1];

                URL Url;
                Drawable drawable = null;
                try {
                    Url = new URL(url);
                    InputStream in = Url.openStream();
                    // 保存到文件
                    saveBitmap2File(in, cacheDirPath, fileName);
                    in.close();

                    File file = new File(cacheDirPath + fileName);
                    if (file.exists()) {
                        Bitmap bitmap = BitmapUtils.createImageThumbnail(
                                cacheDirPath + fileName);
                        // 保存到缓存
                        if (!LCApplication.mTaskDetailImageMap
                                .containsKey(url)) {
                            LCApplication.mTaskDetailImageMap.put(url,
                                    new SoftReference<Bitmap>(bitmap));
                        }

                        drawable = new BitmapDrawable(bitmap);
                        drawable.setBounds(getDefaultBitmapRounds(drawable, context));
                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return drawable;
            }

            @Override
            protected void onPostExecute(Drawable result) {

                if (result != null) {
                    mUrlDrawable.drawable = result;
                    MarkdownImageGetter.this.textView.invalidate();

                    MarkdownImageGetter.this.textView.setHeight((MarkdownImageGetter.this.textView.getHeight()
                            + result.getIntrinsicHeight()));
                }
            }
        }

        public class URLDrawable extends BitmapDrawable {

            protected Drawable drawable;

            @Override
            public void draw(Canvas canvas) {
                if (drawable != null) {
                    drawable.draw(canvas);
                }
            }
        }
        //图片显示大小
        public Rect getDefaultBitmapRounds(Drawable drawable, Context context) {
            int right = 0;
            int bottom = 0;
            float lengthWidthRatio;
            WindowManager windowManager = ((Activity)context).getWindowManager();
            if (drawable != null) {
                lengthWidthRatio = (float) drawable.getIntrinsicHeight() / (float) drawable.getIntrinsicWidth();
                right = windowManager.getDefaultDisplay().getWidth() - 2 * UnitConversion.dp2px(context, 16);
                bottom = (int)(right * lengthWidthRatio);
            }
            return new Rect(0, 0, right, bottom);
        }

    }
}

其中,LCApplication.mTaskDetailImageMap是一个key为url,value为java弱引用的HaskMap,作用显而易见。

另外,有可能图片过大,所以无论如何,我都会对图片做处理,压缩之后的图片长宽最大都是800px,以下是图片压缩的代码:

public static Bitmap createImageThumbnail(String filePath) {
        Bitmap bitmap = null;
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, opts);
        opts.inSampleSize = computeSampleSize(opts, -1, 800 * 800);
        opts.inJustDecodeBounds = false;
        try {
            bitmap = BitmapFactory.decodeFile(filePath, opts);
        } catch (Exception e) {
            // TODO: handle exception
        }
        return bitmap;
    }

    public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
        int roundedSize;
        if (initialSize <= 8) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 8 * 8;
        }
        return roundedSize;
    }

    private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        double w = options.outWidth;
        double h = options.outHeight;
        int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
        int upperBound = (minSideLength == -1) ? 800 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
        if (upperBound < lowerBound) {
            // return the larger one when there is no overlapping zone.
            return lowerBound;
        }
        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }

关于ImageGetter我没有过多的文字描述,各位看代码就好,如果我所写的有什么不对不好不清楚的地方,欢迎交流。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值