Android进阶Android开发经验谈终端研发部

android 中使用ImageGetter在TextView中

2017-11-01  本文已影响78人  追梦小乐

今天在做项目的时候,接口返回的信息是html格式的,一开始我是用textView.setText(Html.fromHtml("html标签字符串",null,null));但是发现html标签里有img标签,里面还有一个图片的链接url,如下所示:
<img title="058B4D41236B9980FC1C07C9EC49872E.png" src="http://www.hbxyjob.cn/cmsfiles/1/image/public/201708/20170824093509_2avfad9vwc.png"/>
尼玛,这怎么显示呢?于是在网上找了很久资料,改了一下代码,实现的效果如下:

HtmlGetter.gif
1、思路分析
2、DownLoadHtmlImageUtils 图片下载工具类
/**
 * 项目名:xyjyyth
 * 包名:  com.tecsun.tsb.utils
 * 文件名:DownLoadHtmlImageUtils
 * 创建者:ZhuiMengXiaoLe
 * 日期:  2017/10/31 18:59
 * 描述:  下载Html标签里面的图片
 */
public class DownLoadHtmlImageUtils {
    public volatile boolean isCancle = false;
    // Init Hander
    EventHandler mHandler = new EventHandler(this);

    /**
     * DownLoad the file that must have a url
     *
     * @param url The http url
     * @param savePath The save path
     */
    public void download(final String url, final String savePath) {
        Log.i("DEBUG", url+"  Start!");
        new Thread(new Runnable() {
            public void run() {
                try {
                    Log.i("DEBUG", url+"  Start !!!!");
                    sendMessage(FILE_DOWNLOAD_CONNECT);
                    URL sourceUrl = new URL(url);
                    URLConnection conn = sourceUrl.openConnection();
                    InputStream inputStream = conn.getInputStream();

                    int fileSize = conn.getContentLength();

                    File savefile = new File(savePath);
                    if (savefile.exists()) {
                        savefile.delete();
                    }
                    savefile.createNewFile();

                    FileOutputStream outputStream = new FileOutputStream(savePath);

                    byte[] buffer = new byte[1024];
                    int readCount = 0;
                    int readNum = 0;
                    int prevPercent = 0;
                    while ((readNum = inputStream.read(buffer)) != -1) {
                        if (readNum > -1) {
                            readCount = readCount + readNum;
                            outputStream.write(buffer, 0, readNum);

                            int percent = (readCount * 100 / fileSize);
                            if (percent > prevPercent) {
                                // send the progress
                                sendMessage(FILE_DOWNLOAD_UPDATE, percent, readCount);
                                prevPercent = percent;
                            }

                            if (isCancle) {
                                outputStream.close();
                                sendMessage(FILE_DOWNLOAD_ERROR, new Exception("Stop"));
                                break;
                            }
                        }
                    }

                    outputStream.close();
                    if (!isCancle) {
                        sendMessage(FILE_DOWNLOAD_COMPLETE, url);
                    }

                } catch (Exception e) {
                    sendMessage(FILE_DOWNLOAD_ERROR, e);
                    // Log.e("MyError", e.toString());
                }
            }
        }).start();
    }

    /**
     * send message to handler
     *
     * @param what handler what
     * @param obj handler obj
     */
    private void sendMessage(int what, Object obj) {
        // init the handler message
        Message msg = mHandler.obtainMessage(what, obj);
        // send message
        mHandler.sendMessage(msg);
    }

    private void sendMessage(int what) {
        Message msg = mHandler.obtainMessage(what);
        mHandler.sendMessage(msg);
    }

    private void sendMessage(int what, int arg1, int arg2) {
        Message msg = mHandler.obtainMessage(what, arg1, arg2);
        mHandler.sendMessage(msg);
    }

    public void setCancle() {
        this.isCancle = true;
        Log.v("setCancle", String.valueOf(isCancle));
    }

    private static final int FILE_DOWNLOAD_CONNECT = 0;
    private static final int FILE_DOWNLOAD_UPDATE = 1;
    private static final int FILE_DOWNLOAD_COMPLETE = 2;
    private static final int FILE_DOWNLOAD_ERROR = -1;

    // defined the handler
    private class EventHandler extends Handler {
        private final DownLoadHtmlImageUtils mManager;

        public EventHandler(DownLoadHtmlImageUtils manager) {
            mManager = manager;
        }

        // do the receive message
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what) {
                case FILE_DOWNLOAD_CONNECT:
                    if (mOnDownloadListener != null)
                        mOnDownloadListener.onDownloadConnect(mManager);
                    break;
                case FILE_DOWNLOAD_UPDATE:
                    if (mOnDownloadListener != null)
                        mOnDownloadListener.onDownloadUpdate(mManager, msg.arg1);
                    break;
                case FILE_DOWNLOAD_COMPLETE:
                    if (mOnDownloadListener != null)
                        mOnDownloadListener.onDownloadComplete(mManager, msg.obj);
                    break;
                case FILE_DOWNLOAD_ERROR:
                    if (mOnDownloadListener != null)
                        mOnDownloadListener.onDownloadError(mManager, (Exception) msg.obj);
                    break;
                default:
                    break;
            }
        }
    }

    // defined connection listener
    private OnDownloadListener mOnDownloadListener;

    public interface OnDownloadListener {
        void onDownloadConnect(DownLoadHtmlImageUtils manager);
        void onDownloadUpdate(DownLoadHtmlImageUtils manager, int percent);
        void onDownloadComplete(DownLoadHtmlImageUtils manager, Object result);
        void onDownloadError(DownLoadHtmlImageUtils manager, Exception e);
    }

    public void setOnDownloadListener(OnDownloadListener listener) {
        mOnDownloadListener = listener;
    }
}
3、核心代码1分析:设置TextView可点击跳转Html里面的链接
    //保存文件路径
    private final String path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/htmlimg/";
        //设置链接中标签是可以点击并且可以跳转到浏览器打开的
        tvJobDes.setClickable(true);
        tvJobDes.setMovementMethod(LinkMovementMethod.getInstance());
        //整个TextView居中显示
//      tvJobDes.setGravity(Gravity.CENTER_HORIZONTAL);

4、核心代码2分析:第一次赋值给 TextView,同时开始下载图片
private void setData() {
        //初始化下载类
        downLoadUtils=new DownLoadHtmlImageUtils();
        //设置下载类监听事件
        downLoadUtils.setOnDownloadListener(onDownloadListener);
        //给Textview赋值
        tvJobDes.setText(Html.fromHtml(mFvalue,imageGetter,null));
    }
    Drawable drawable = null;
    Bitmap bitmap = null;
    Html.ImageGetter imageGetter = new Html.ImageGetter() {
        public Drawable getDrawable(String source) {

            String fileString=path+String.valueOf(source.hashCode());
            Log.i("DEBUG", fileString+"");
            Log.i("DEBUG", source+"");

            if (!new File(path).exists()){
                new File(path).mkdirs();
                LogUtils.d(TAG,"创建文件夹成功========");
            }
            //判断SD卡里面是否存在图片文件
            if (new File(fileString).exists()) {
                Log.i("DEBUG", fileString+"  eixts");
                //获取本地文件返回Drawable
                bitmap = BitmapFactory.decodeFile(fileString);
                drawable = new BitmapDrawable(bitmap);
//              drawable=Drawable.createFromPath(fileString);
                //设置图片边界
                if (drawable != null){
                    drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
                    return drawable;
                }
                return null;

            }else {
                Log.i("DEBUG", fileString+" Do not eixts");
                //启动新线程下载
                downLoadUtils.download(source, path+String.valueOf(source.hashCode()));
                return drawable;
            }

        };

    };
5、核心代码3分析:等待图片下载完成的时候,再一次赋值给TextView
DownLoadHtmlImageUtils.OnDownloadListener onDownloadListener=new DownLoadHtmlImageUtils.OnDownloadListener() {

        //下载进度
        public void onDownloadUpdate(DownLoadHtmlImageUtils manager, int percent) {
            // TODO Auto-generated method stub
            Log.i("DEBUG", percent+"");
        }

        //下载失败
        public void onDownloadError(DownLoadHtmlImageUtils manager, Exception e) {
            // TODO Auto-generated method stub

        }

        //开始下载
        public void onDownloadConnect(DownLoadHtmlImageUtils manager) {
            // TODO Auto-generated method stub
            Log.i("DEBUG", "Start  //////");
        }

        //完成下载
        public void onDownloadComplete(DownLoadHtmlImageUtils manager, Object result) {
            // TODO Auto-generated method stub
            Log.i("DEBUG", result.toString());
            //替换sTExt的值,就是把图片的网络路径换成本地SD卡图片路径(最早想法,可以不需要这样做了)
            //sText.replace(result.toString(), path+String.valueOf(result.hashCode()));
            //再一次赋值给Textview
            tvJobDes.setText(Html.fromHtml(mFvalue, imageGetter, null));
        }
    };
6、核心代码4分析:退出Activity或者Fragment的时候,回收Bitmap
@Override
    protected void onDestroy() {
        super.onDestroy();
        drawable = null;

//因为本应用要下载图片太多,所以每次退出的时候把下载好的本地图片删除
        FileUtils.delDir(path);
        if (bitmap != null && !bitmap.isRecycled()){
            bitmap.recycle();
            bitmap = null;
            drawable = null;
        }

        if (imageGetter != null){
            imageGetter = null;
        }
    }
8、FileUtils工具类核心方法:为了删除下载好的本地图片
 /**
     * 删除方法 这里只会删除某个文件夹下的文件<br/>
     * 支持两级目录删除
     */
    public static void delDir(String path) {
            File directory = new File(path);
            if (directory != null && directory.exists() && directory.isDirectory()) {
                for (File item : directory.listFiles()) {
                    if (item.isDirectory()) {
                        for (File img : item.listFiles()) {
                            img.delete();
                        }
                    }
                    item.delete();
                }
            }
    }
8、FileUtils工具类完整代码
public class FileUtils {
    private static Context mContext;
    private static final int DELAY_TIME = 10000;

    public final static String CACHE_DIR = "CNMusic";
    public final static String GLIDE_CACHE_DIR = "glide";
    public final static String SONG_CACHE_DIR = "songs";

    public final static String APK_NAME = "cnmusic.apk";


    /**
     * 应用关联的图片存储空间
     *
     * @param context
     * @return
     */
    public static String getAppPictureDir(Context context) {
        File file = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        return file.getPath();
    }

    /**
     * 获取缓存主目录
     *
     * @return
     */
    public static String getMountedCacheDir() {
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            // 创建一个文件夹对象,赋值为外部存储器的目录
            File sdcardDir = Environment.getExternalStorageDirectory();
            //得到一个路径,内容是sdcard的文件夹路径和名字
            String path = sdcardDir.getPath() + File.separator + CACHE_DIR;
            File path1 = new File(path);
            if (!path1.exists()) {
                path1.mkdirs();
            }
            return path1.getPath();
        }
        return null;
    }

    /**
     * 获取存放歌曲的目录
     *
     * @return
     */
    public static String getSongDir() {
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            String path = getMountedCacheDir() + File.separator + SONG_CACHE_DIR;
            File path1 = new File(path);
            if (!path1.exists()) {
                path1.mkdirs();
            }
            return path1.getPath();
        }
        return null;
    }

    /**
     * 获取存放缓存的目录
     *
     * @return
     */
    public static String getCacheDir() {
        File file = mContext.getExternalCacheDir();
        return file.getPath();
    }

    /**
     * 删除方法 这里只会删除某个文件夹下的文件<br/>
     * 支持两级目录删除
     */
    public static void cleanCacheDir() {
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            File directory = mContext.getExternalCacheDir();
            if (directory != null && directory.exists() && directory.isDirectory()) {
                for (File item : directory.listFiles()) {
                    if (item.isDirectory()) {
                        for (File img : item.listFiles()) {
                            img.delete();
                        }
                    }
                    item.delete();
                }
            }
        }
    }

    /**
     * 删除方法 这里只会删除某个文件夹下的文件<br/>
     * 支持两级目录删除
     */
    public static void delDir(String path) {
            File directory = new File(path);
            if (directory != null && directory.exists() && directory.isDirectory()) {
                for (File item : directory.listFiles()) {
                    if (item.isDirectory()) {
                        for (File img : item.listFiles()) {
                            img.delete();
                        }
                    }
                    item.delete();
                }
            }
    }


    /**
     * 获取apk放置的地址
     *
     * @return
     */
    public static String getApkPath() {
        String apkPath = getCacheDir() + File.separator + APK_NAME;
        File file = new File(apkPath);
        if (file.exists()) {
            file.delete();
        }
        return file.getPath();
    }

    /**
     * 读取Assets目录下的文件
     *
     * @param context
     * @param name
     * @return
     */
    public static String getAssets(Context context, String name) {
        String result = null;
        try {
            InputStream in = context.getAssets().open(name);  //获得AssetManger 对象, 调用其open 方法取得  对应的inputStream对象
            int size = in.available();//取得数据流的数据大小
            byte[] buffer = new byte[size];
            in.read(buffer);
            in.close();
            result = new String(buffer);
        } catch (Exception e) {
        }
        return result;
    }

    /**
     * 媒体扫描,防止下载后在sdcard中获取不到歌曲的信息
     *
     * @param path
     */
    public static void mp3Scanner(String path) {
        MediaScannerConnection.scanFile(mContext.getApplicationContext(),
                new String[]{path}, null, null);
    }

    public static boolean existFile(String path) {
        File path1 = new File(path);
        return path1.exists();
    }

    public static boolean deleteFile(String path) {
        File path1 = new File(path);
        if (path1.exists()) {
            return path1.delete();
        }
        return false;
    }

    /**
     * open apk
     *
     * @param context
     * @param apk
     */
    public static void openApk(Context context, File apk) {
        Intent intent = new Intent();
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(apk),
                "application/vnd.android.package-archive");
        context.startActivity(intent);
    }


    /**
     * 获取下载文件的大小
     *
     * @param soFarBytes 已下载字节
     * @param totalBytes 总共的字节
     * @return
     */
    public static String getProgressSize(long soFarBytes, long totalBytes) {
        float progress = soFarBytes * 1.0f / 1024 / 1024;
        float total = totalBytes * 1.0f / 1024 / 1024;
        String format = "%.1fM/%.1fM";
        String str = String.format(Locale.CHINA, format, progress, total);
        return str;
    }

    /**
     * 获取下载进度
     *
     * @param soFarBytes 已下载字节
     * @param totalBytes 总共的字节
     * @return
     */
    public static int getProgress(long soFarBytes, long totalBytes) {
        if (totalBytes != 0) {
            long progress = soFarBytes * 100 / totalBytes;
            return (int) progress;
        }
        return 0;
    }

    /**
     * download apk from server
     *
     * @param path the apk path
     * @param fp   file progress listener
     * @return apk file
     * @throws Exception
     */
    public static File getFilefromServerToProgress(String path, FileProgress fp) throws Exception {
        //如果相等的话表示当前的sdcard挂载在手机上并且是可用的
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            URL url = new URL(path);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(DELAY_TIME);
            int max = conn.getContentLength();
            InputStream is = conn.getInputStream();
            File file = new File(Environment.getExternalStorageDirectory(), "beats.apk");
            FileOutputStream fos = new FileOutputStream(file);
            BufferedInputStream bis = new BufferedInputStream(is);
            byte[] buffer = new byte[1024];
            int len;
            int total = 0;
            while ((len = bis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
                total += len;
                //获取当前下载量
                if (fp != null)
                    fp.getProgress(total, max);
            }
            fos.close();
            bis.close();
            is.close();
            return file;
        } else {
            return null;
        }
    }

    private static Pattern FilePattern = Pattern.compile("[\\\\/:*?\"<>|]");

    public static String filenameFilter(String str) {
        return str == null ? null : FilePattern.matcher(str).replaceAll("");
    }


    public interface FileProgress {
        void getProgress(int total, int max);
    }



}
9、主Actiivity或者Fragment完整代码实现
**
 * 发布信息详情查询
 * @author zlc
 *
 */
public class JobFbxxxqcxActivity extends BaseActivity {

    private static final String TAG = JobFbxxxqcxActivity.class.getSimpleName();
    private Context mContext;
    private TextView tvJobName;
    private TextView tvJobDes;
    private TextView tvJobTask;
    private TextView tvJobRequest;
    private ImageButton mImbBack;
    private JobIntentInfoBean mInfoBean;
    private String mFinfoid;
    private String mFvalue;
    private DownLoadHtmlImageUtils downLoadUtils;
    //保存文件路径
    private final String path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/htmlimg/";


    //  String sText = "测试图片信息:<br><img src=\"http://pic004.cnblogs.com/news/201211/20121108_091749_1.jpg\" />";

    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
        setContentView(R.layout.activity_job_fbxxcx_32);
        AppApplication.getInstance().addActivity(this);
        initView();

        initListener();

        initData();
        
        getListData();
    }


    /**
     * 初始化数据的方法
     */
    private void initData() {
        mInfoBean = (JobIntentInfoBean) getIntent().getSerializableExtra
                (Constant.INTENT_CONTENT);
        if (mInfoBean != null){
            mFinfoid = mInfoBean.finfoid;
            LogUtils.d(TAG,"mFinfoid===="+mFinfoid);
        }
    }

    /**
     * 初始化监听的方法
     */
    private void initListener() {
        mImbBack.setOnClickListener(this);
    }

    @Override
    protected void initView() {
        mContext = this;
        loadingDialog = new LoadingDialog(this, R.string.tip_loading_msg);
        TextView tvTitle = (TextView) this.findViewById(R.id.tv_base_title_content);
        tvTitle.setText(R.string.title_fbxxxq);
        mImbBack = (ImageButton) this.findViewById(R.id.imgb_back);
        tvJobName = (TextView) findViewById(R.id.tv_job_name);
        tvJobDes = (TextView) findViewById(tv_job_des);
        tvJobTask = (TextView) findViewById(R.id.tv_job_task);
        tvJobRequest = (TextView) findViewById(R.id.tv_job_request);

        //设置链接中标签是可以点击并且可以跳转到浏览器打开的
        tvJobDes.setClickable(true);
        tvJobDes.setMovementMethod(LinkMovementMethod.getInstance());
        //整个TextView居中显示
//      tvJobDes.setGravity(Gravity.CENTER_HORIZONTAL);

    }
    
    @Override
    public void onClick(View v) {
        super.onClick(v);
        switch (v.getId()) {
            case R.id.imgb_back:
                finish();
                EventBus.getDefault().post(true);
        }
    }

    /**
     * 查询个人求职列表信息
     */
    private void getListData() {
        showLoadingDialog();
        StringEntity entity = new JobParam().getCmsInfoParam(mFinfoid);
        HttpUtil.post(this, APICommon.API_GETCMSINFO, entity, new AsyncHttpResponseHandler() {
            
            @Override
            public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                LogUtils.d(new String(responseBody));
                Gson gson = new Gson();
                FbxxxqDataListResponse bean = gson.fromJson(new String
                        (responseBody), FbxxxqDataListResponse.class);
                loadPersionData(bean);
                dismissLoadingDialog();
            }
            
            @Override
            public void onFailure(int statusCode, Header[] headers,
                    byte[] responseBody, Throwable error) {
                dismissLoadingDialog();

            }
        });
    }
    
    /**
     * 界面加载数据
     * @param bean
     */
    @SuppressWarnings("unchecked")
    private void loadPersionData(FbxxxqDataListResponse bean) {
        if (!bean.isSuccess()) {

            return;
        }
        if (bean != null) {
            List<InfoidDataResponse> infoidDataResponseList = bean.data;
            if (infoidDataResponseList.size() > 0){
                InfoidDataResponse infoidDataResponse = infoidDataResponseList.get(0);
                mFvalue = infoidDataResponse.getFvalue();
                if (!TextUtils.isEmpty(mFvalue)){
                    setData();
                }else {
                    showWarnStrDialog("发布信息详情为空");
                }

            }
        }
    }

    private void setData() {
        //初始化下载类
        downLoadUtils=new DownLoadHtmlImageUtils();
        //设置下载类监听事件
        downLoadUtils.setOnDownloadListener(onDownloadListener);
        //给Textview赋值
        tvJobDes.setText(Html.fromHtml(mFvalue,imageGetter,null));
    }
    Drawable drawable = null;
    Bitmap bitmap = null;
    Html.ImageGetter imageGetter = new Html.ImageGetter() {
        public Drawable getDrawable(String source) {

            String fileString=path+String.valueOf(source.hashCode());
            Log.i("DEBUG", fileString+"");
            Log.i("DEBUG", source+"");

            if (!new File(path).exists()){
                new File(path).mkdirs();
                LogUtils.d(TAG,"创建文件夹成功========");
            }
            //判断SD卡里面是否存在图片文件
            if (new File(fileString).exists()) {
                Log.i("DEBUG", fileString+"  eixts");
                //获取本地文件返回Drawable
                bitmap = BitmapFactory.decodeFile(fileString);
                drawable = new BitmapDrawable(bitmap);
//              drawable=Drawable.createFromPath(fileString);
                //设置图片边界
                if (drawable != null){
                    drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
                    return drawable;
                }
                return null;

            }else {
                Log.i("DEBUG", fileString+" Do not eixts");
                //启动新线程下载
                downLoadUtils.download(source, path+String.valueOf(source.hashCode()));
                return drawable;
            }

        };

    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        drawable = null;
        FileUtils.delDir(path);
        if (bitmap != null && !bitmap.isRecycled()){
            bitmap.recycle();
            bitmap = null;
            drawable = null;
        }

        if (imageGetter != null){
            imageGetter = null;
        }
    }

    Html.TagHandler MyTagHandler  = new Html.TagHandler() {
        @Override
        public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {

        }
    };

    DownLoadHtmlImageUtils.OnDownloadListener onDownloadListener=new DownLoadHtmlImageUtils.OnDownloadListener() {

        //下载进度
        public void onDownloadUpdate(DownLoadHtmlImageUtils manager, int percent) {
            // TODO Auto-generated method stub
            Log.i("DEBUG", percent+"");
        }

        //下载失败
        public void onDownloadError(DownLoadHtmlImageUtils manager, Exception e) {
            // TODO Auto-generated method stub

        }

        //开始下载
        public void onDownloadConnect(DownLoadHtmlImageUtils manager) {
            // TODO Auto-generated method stub
            Log.i("DEBUG", "Start  //////");
        }

        //完成下载
        public void onDownloadComplete(DownLoadHtmlImageUtils manager, Object result) {
            // TODO Auto-generated method stub
            Log.i("DEBUG", result.toString());
            //替换sTExt的值,就是把图片的网络路径换成本地SD卡图片路径(最早想法,可以不需要这样做了)
            //sText.replace(result.toString(), path+String.valueOf(result.hashCode()));
            //再一次赋值给Textview
            tvJobDes.setText(Html.fromHtml(mFvalue, imageGetter, null));
        }
    };
}
11、存在的问题

哪位大神有知道如何实现的,麻烦告知下

12、参考文章

1、https://app.yinxiang.com/shard/s71/nl/14923130/5d9a8167-f62a-45b7-94ae-7ac0318021ba/
2、https://app.yinxiang.com/shard/s71/nl/14923130/0af86dd4-3751-4a6c-b911-c2d170787ba4/
3、https://app.yinxiang.com/shard/s71/nl/14923130/446ec74b-41ad-4b7e-bedc-e7f2ddb09228/
4、https://app.yinxiang.com/shard/s71/nl/14923130/3acfca20-fc98-4742-ad36-bded31dc754b/
ps:本博文的代码有一部分是沿用上面大神的代码,有些是在上面的基础上进行了修改

上一篇下一篇

猜你喜欢

热点阅读