zoukankan      html  css  js  c++  java
  • Diycode开源项目 ImageActivity分析

    1.首先看一下效果

    1.1做成了一个GIF

      

     1.2.我用格式工厂有点问题,大小无法调到手机这样的大小,目前还没有解决方案。

     1.3.网上有免费的MP4->GIF,参考一下这个网站吧。 

     1.4.讲解一下这个图片吧,首先是从话题中点击了其中一张图片,进入图片Activity,

       然后可以自由放大,自由翻转。


    2.分析一下继承的BaseImageActivity

    2.1因为ImageActivity继承了BaseImageActivtiy,首先看看源代码。

    /*
     * Copyright 2017 GcsSloop
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *    http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     *
     * Last modified 2017-03-15 20:02:59
     *
     * GitHub:  https://github.com/GcsSloop
     * Website: http://www.gcssloop.com
     * Weibo:   http://weibo.com/GcsSloop
     */
    
    package com.gcssloop.diycode.base.app;
    
    import android.content.Intent;
    
    import java.util.ArrayList;
    
    /**
     * 对数据进行预处理
     */
    public abstract class BaseImageActivity extends BaseActivity {
        public static final String ALL_IMAGE_URLS = "all_images";
        public static final String CURRENT_IMAGE_URL = "current_image";
    
        protected static final String MODE_NORMAL = "normal";
        protected static final String MODE_ERROR = "error";
        protected String mCurrentMode = MODE_NORMAL;
    
        protected ArrayList<String> images = new ArrayList<>();     // 所有图片
        protected String current_image_url = null;                  // 当前图片
        protected int current_image_position = 0;                  // 当前图片位置
    
        @Override
        protected void initDatas() {
            super.initDatas();
    
            // 初始化图片 url 和 图片集合,保证两个数据都存在
            Intent intent = getIntent();
    
            // 没有传递当前图片,设置模式为错误
            String imageUrl = intent.getStringExtra(CURRENT_IMAGE_URL);
            if (imageUrl == null || imageUrl.isEmpty()) {
                toastShort("没有传递图片链接");
                mCurrentMode = MODE_ERROR;
                return;
            }
            mCurrentMode = MODE_NORMAL;
    
            ArrayList<String> temp = intent.getStringArrayListExtra(ALL_IMAGE_URLS);
            if (temp == null || temp.size() <= 0) {
                // 记录当前图片,计算位置
                images.clear();
                images.add(imageUrl);
            } else if (temp.size() > 0) {
                // 如果图片集合大于0
                images = new ArrayList<>(temp);
            }
    
            if (!images.contains(imageUrl)) {
                images.add(imageUrl);
            }
    
            current_image_url = imageUrl;
            current_image_position = images.indexOf(current_image_url);
        }
    }
    View Code

    2.2这里需要回顾一下BaseActivity。

      BaseActivity==>主要提供了项目本身API提供的Diycode类,ViewHolder类,Toast类,两个抽象方法。ActionBar。结束

    当前活动。

        打开活动。

    2.3.私有成员变量。

      ·存放图片链接的一个ArrayList<String>

       ·存放当前图片的链接current_image_url

      ·存放当前图片位置,整型current_image_positon

    2.4.注意点,Override一个在基类中已经实现过的函数。

      重载,也是可以Override的。以前理解的Override就是抽象方法,接口方法。这里又涨知识了。

      如果写了一个父类中有的方法,就相当于重载了父类中的方法,就不会执行父类中的那个方法,而是执行自己的方法了。

    名字是一样的。

    2.5.在BaseActivity中写了一个空的initDatas方法。而且规定了这个方法执行的顺序。

      然后在子类BaseImageActivity中重载这个方法。

      这个类主要处理图片数据的。

    2.6分析这个重载方法。

      

      

      2.6.1首先从intent中获得当前图片,如果没有,设置模式为错误。否则,设置模式为正常。

      2.6.2然后从intent中获得所有图片集合,如果没有,就把当前图片加进images去,如果有,就将集合加到一个images中。

      2.6.3然后它还判断了一下images中是否包含了当前url,应该是双保险吧,感觉没必要。

      2.6.4最后,就把当前图片和当前位置更新了一下。


    3.分析一下图片缓存DiskImageCache

    3.1首先看看在ImageActivity中定义的一个变量。DiskImageCache mCache;源代码如下:

    /*
     * Copyright 2017 GcsSloop
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *    http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     *
     * Last modified 2017-03-12 00:56:52
     *
     * GitHub:  https://github.com/GcsSloop
     * Website: http://www.gcssloop.com
     * Weibo:   http://weibo.com/GcsSloop
     */
    
    package com.gcssloop.diycode.base.webview;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Environment;
    import android.os.StatFs;
    import android.support.annotation.NonNull;
    import android.util.Log;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;
    import java.util.Comparator;
    
    public class DiskImageCache {
        private static final String CACHE_SUFFIX = ".cache";
    
        private static final int MB = 1024 * 1024;
        private static final int CACHE_SIZE = 50;   // 缓存占用空间大小
        private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;    // 为 SD 卡保留多少空间
    
        private File cacheDir;
    
        public DiskImageCache(Context context) {
            cacheDir = getDiskCacheDir(context, "web-image");
            // 整理缓存
            organizeCache(cacheDir);
        }
    
        /**
         * 从缓存中获取图片
         **/
        public Bitmap getBitmap(final String key) {
            final String path = getCachePath(key);
            File file = new File(path);
            if (file.exists()) {
                Bitmap bmp = BitmapFactory.decodeFile(path);
                if (bmp == null) {
                    file.delete();
                } else {
                    updateFileTime(path);
                    return bmp;
                }
            }
            return null;
        }
    
        /**
         * 将图片存入文件缓存
         **/
        public void saveBitmap(String key, Bitmap bm) {
            if (bm == null) {
                return;
            }
            //判断sdcard上的空间
            if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
                return;     //SD空间不足
            }
            File file = new File(getCachePath(key));
            try {
                file.createNewFile();
                OutputStream outStream = new FileOutputStream(file);
                bm.compress(Bitmap.CompressFormat.PNG, 100, outStream);
                outStream.flush();
                outStream.close();
            } catch (FileNotFoundException e) {
                Log.w("ImageFileCache", "FileNotFoundException");
            } catch (IOException e) {
                Log.w("ImageFileCache", "IOException");
            }
        }
    
        /**
         * 保存 bytes 数据
         *
         * @param key   url
         * @param bytes bytes 数据
         */
        public void saveBytes(String key, byte[] bytes) {
            if (bytes == null) {
                return;
            }
            //判断sdcard上的空间
            if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
                return;     //SD空间不足
            }
            File file = new File(getCachePath(key));
            try {
                file.createNewFile();
                OutputStream outStream = new FileOutputStream(file);
                outStream.write(bytes);
                outStream.flush();
                outStream.close();
            } catch (FileNotFoundException e) {
                Log.w("ImageFileCache", "FileNotFoundException");
            } catch (IOException e) {
                Log.w("ImageFileCache", "IOException");
            }
        }
    
        /**
         * 获取一个本地缓存的输入流
         *
         * @param key url
         * @return FileInputStream
         */
        public FileInputStream getStream(String key) {
            File file = new File(getCachePath(key));
            if (!file.exists())
                return null;
            try {
                FileInputStream inputStream = new FileInputStream(file);
                return inputStream;
            } catch (FileNotFoundException e) {
                Log.e("getStream", "FileNotFoundException");
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 获取本地缓存路径
         *
         * @param key url
         * @return 路径
         */
        public String getDiskPath(String key) {
            File file = new File(getCachePath(key));
            if (!file.exists())
                return null;
            return file.getAbsolutePath();
        }
    
        /**
         * 是否有缓存
         *
         * @param key url
         * @return 是否有缓存
         */
        public boolean hasCache(String key) {
            File file = new File(getCachePath(key));
            return file.exists();
        }
    
        /**
         * 计算存储目录下的文件大小,
         * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
         * 那么删除40%最近没有被使用的文件
         */
        private boolean organizeCache(@NonNull File cacheDir) {
            File[] files = cacheDir.listFiles();
            if (files == null) {
                return true;
            }
            if (!Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                return false;
            }
    
            int dirSize = 0;
            for (int i = 0; i < files.length; i++) {
                if (files[i].getName().contains(CACHE_SUFFIX)) {
                    dirSize += files[i].length();
                }
            }
    
            if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
                int removeFactor = (int) ((0.4 * files.length) + 1);
                Arrays.sort(files, new FileLastModifSort());
                for (int i = 0; i < removeFactor; i++) {
                    if (files[i].getName().contains(CACHE_SUFFIX)) {
                        files[i].delete();
                    }
                }
            }
    
            if (freeSpaceOnSd() <= CACHE_SIZE) {
                return false;
            }
    
            return true;
        }
    
        /**
         * 修改文件的最后修改时间
         **/
        public void updateFileTime(String path) {
            File file = new File(path);
            long newModifiedTime = System.currentTimeMillis();
            file.setLastModified(newModifiedTime);
        }
    
        /**
         * 计算sdcard上的剩余空间
         **/
        private int freeSpaceOnSd() {
            StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
            double sdFreeMB = ((double) stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
            return (int) sdFreeMB;
        }
    
        /**
         * 根据文件的最后修改时间进行排序
         */
        private class FileLastModifSort implements Comparator<File> {
            public int compare(File arg0, File arg1) {
                if (arg0.lastModified() > arg1.lastModified()) {
                    return 1;
                } else if (arg0.lastModified() == arg1.lastModified()) {
                    return 0;
                } else {
                    return -1;
                }
            }
        }
    
    
        /**
         * 获取缓存文件的绝对路径
         */
        private String getCachePath(String key) {
            return cacheDir.getAbsolutePath() + File.separator + convertKey(key);
        }
    
        /**
         * 获取磁盘缓存文件夹 优先获取外置磁盘
         *
         * @param context    上下文
         * @param uniqueName 自定义名字
         */
        public File getDiskCacheDir(Context context, String uniqueName) {
            String cachePath;
            if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                    || !Environment.isExternalStorageRemovable()) {
                cachePath = context.getExternalCacheDir().getPath();
            } else {
                cachePath = context.getCacheDir().getPath();
            }
            File cacheDir = new File(cachePath + File.separator + uniqueName);
            if (!cacheDir.exists())
                cacheDir.mkdir();
            return cacheDir;
        }
    
        /**
         * 哈希编码
         */
        public String convertKey(String key) {
            String cacheKey;
            try {
                final MessageDigest mDigest = MessageDigest.getInstance("MD5");
                mDigest.update(key.getBytes());
                cacheKey = bytesToHexString(mDigest.digest());
            } catch (NoSuchAlgorithmException e) {
                cacheKey = String.valueOf(key.hashCode());
            }
            return cacheKey + CACHE_SUFFIX;
        }
    
        private String bytesToHexString(byte[] bytes) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                String hex = Integer.toHexString(0xFF & bytes[i]);
                if (hex.length() == 1) {
                    sb.append('0');
                }
                sb.append(hex);
            }
            return sb.toString();
        }
    
    }
    View Code

    3.2定义的常量及变量预览。

        

    3.3.构造函数就一个。实现方法在后面。

      

    3.4.如何从缓存中获取图片(通过一个String返回一个Bitmap),这个项目暂时没有用到。

      

    3.5.如何将图片存入文件缓存?(将一个bitmap,存放一个缓存路径为一个key下)

      

    3.6.如何保存bytes数据?(也是存放到一个key中)

      

    3.7.如何获取一个本地缓存的输入流?(通过一个key获得FileInputStream)

      

    3.8.如何获取本地缓存路径?(通过一个key得到一个String)

      

    3.9.如何判断是否有缓存?(通过一个key返回boolean)

      

    3.10.然后是关键的整理存储空间了。这个删除文件很关键,不然数据会越来越大的。

      

      

    3.11.如何修改文件的最后修改时间?(参数为文件path)

      

    3.12.如何计算sdcard上的剩余空间?

      

    3.13.如何根据文件的最后修改时间进行排序?(继承了一个Comparator<File>的内部类)

      

      FileLastModifSort其实就是一个类,类中有一个比较的函数,有两个File的参数,比较哪个更加最后而已。

    3.14.如何获取缓存文件的绝对路径?(通过key得到一个String)

      

    3.15.如何获取磁盘缓存文件夹,优先获取外置磁盘?(参数为context和一个string返回一个File)

      

    3.16.什么是哈希编码?(参数为key返回一个String)

      

      通过MD5加密后加了一个后缀为.cache,返回这个字符串。

    3.17.bytes数组转化为哈希String?(参数为byte[]返回一个String)

      

      这个在3.16.哈希编码中用到了,就是系统函数很多会转化为bytes,这个函数就是将byte[]类型的数据整合成String,

    然后再处理逻辑。

      这个DiskImageCache比较长,主要处理的就是一些缓存问题了,图片缓存,和本地的存储器挂钩了,所以就有很多函数

    要调用。


    4.回到ImageActivity

    4.1这里首先复写了一个getLayoutId,返回一个资源id,其实就是ImageActivity布局

      贴一下activity_image源代码。

      

    <?xml version="1.0" encoding="utf-8"?>
    <!--
      ~ Copyright 2017 GcsSloop
      ~
      ~ Licensed under the Apache License, Version 2.0 (the "License");
      ~ you may not use this file except in compliance with the License.
      ~ You may obtain a copy of the License at
      ~
      ~    http://www.apache.org/licenses/LICENSE-2.0
      ~
      ~ Unless required by applicable law or agreed to in writing, software
      ~ distributed under the License is distributed on an "AS IS" BASIS,
      ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      ~ See the License for the specific language governing permissions and
      ~ limitations under the License.
      ~
      ~ Last modified 2017-03-15 23:45:17
      ~
      ~ GitHub:  https://github.com/GcsSloop
      ~ Website: http://www.gcssloop.com
      ~ Weibo:   http://weibo.com/GcsSloop
      -->
    
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/activity_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.gcssloop.diycode.activity.ImageActivity">
    
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v4.view.ViewPager
                android:id="@+id/view_pager"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
            </android.support.v4.view.ViewPager>
    
            <com.rd.PageIndicatorView
                android:id="@+id/pageIndicator"
                android:layout_marginBottom="16dp"
                android:layout_centerHorizontal="true"
                android:layout_alignParentBottom="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:piv_viewPager="@id/view_pager"
                app:piv_animationType="worm"
                app:piv_autoVisibility="false"
                app:piv_radius="4dp"
                app:piv_padding="12dp"
                app:piv_unselectedColor="@color/diy_gray2"
                app:piv_selectedColor="@color/diy_red"
                />
    
        </RelativeLayout>
    
    </LinearLayout>
    View Code

    4.2分析一下布局代码的结构

      

    4.3.和真实页面对比一下。

      

    4.4.这里有一个新的知识点==>PageIndicatorView

      可以参考一下这篇文章。

      这是一个开源框架。挺不错的。简单实用。

      作用就是当页面在切换时,一个提示作用,下面的小红点会告诉用户这是第几张图或者第几个页面。

    4.5.然后是复写一个initViews方法。

      4.5.1.注意,这个函数在initDatas之后调用。initDatas()函数在BaseImageActivity中调用了。  

      4.5.2.这里面通过setTitle设置了标题栏的查看图片。

      4.5.3.这里从参数ViewHolder中获得了布局中的ViewPager,显然ViewHolder中BaseActivity中定义

         然后ViewHolder初始化其实也是在BaseActivity中进行的,利用了一个抽象函数getLayoutId()

         然后initData(),然后initViews(),所以这个ImageActivity才能直接获得这个ViewPager的Id。

             

      4.5.4.这里看一下initViews中的部分代码。

         

         对于getLayoutInflater不太理解,参考一下这篇文章

         4.5.4.1.它的作用类似于findViewById()。

         4.5.4.2.LayoutInflater是用来找res/layout/下的xml布局文件,并且实例化。

         4.5.4.3.FindViewById是找xml布局文件下的具体widget控件(如Button、TextView等)

      4.5.5.这里继续initViews中的部分代码。

        

        4.5.5.1.这里是在ImageActivity中定义的布局文件中的viewPager设置适配器的过程。

        4.5.5.2.首先是复写了getCount方法,直接返回图片的张数。

        4.5.5.3.然后是复写了isViewFromObject方法,两个参数(View,Object),使两者相等。

        4.5.5.4.然后复写了一个关键的函数,instantiateItem(ViewGroup,int)

           

            对于这个复写函数不明白的,可以参考一下这篇文章。

        4.5.5.6.这里不得不提一下的是PhotoView。

            也是第三方开源库,支持双指缩放,旋转。

            使用方法点击这里

            github原地址点击这里。

            看一下效果图吧!

            

       4.5.5.7.分析一下instantiateItem==>这个方法返回一个对象,这个对象表明了PagerAdapter适配器选

            择那个对象放在当前的ViewPager中。

            然后这里还处理了缓存,如果缓存中有,就利用Glide.with(context).load(file).into()

            如果缓存中没有,就利用Glide.with(context).load(图片url).into()方法       

             然后将这个photoView添加到容器中,这个容器就是instantiateItem中的第一个参数。

       4.5.5.8.分析一下复写的destroyItem()方法,3个参数(ViewGroup,int,Object)

            这个方法,是从ViewGroup中移除当前View。

       4.5.5.9.然后将viewPager确定到第几项。

            调用了基类BaseImageActivity中的静态常量current_image_position。

            这样可以确定指示剂的位置。

     4.6.然后回到ImageActivity中,最后一个函数是loadImage(string,photoView)

          作用:给一个url和一个photoView,加载一张图到photoView中。

          这里用到了一个第三方库。图片加载库。

          Glide用法详解点击这里。

          Glide的原地址点击这里。

          源码如下:

          

          这个应该是设置了缓存策略。

          

          


    5.总结一下

    5.1.首先理解一下哪里会用到ImageActivity这个活动,这个活动不是局限于某一个模块,而是所有地方,只要包含了图片

      的地方,都是有可能调用intent来跳转到这里的。所以这个有点像之前做的大学喵,点击图片查看大图,支持缩放。

    5.2.然后理解一下为什么这个ImageActivity要继承一个BaseImageActivity,这个BaseImageActivity实际上是一个抽象类,

      定义了所有图片数据,所以真实数据在这里面处理,确定当前图片url,当前是第几个图片。

    5.3.然后定义了一个DiskImageCache,主要是处理图片缓存的,基本上每个项目都会涉及到图片缓存,而且这些方法都是

      必须实现而且基本一样,所以今后的项目也可以参考一下这个类,封装好的,耦合很低。

    5.4.然后是一个imageActivity的布局,通过实现了BaseActivity中的抽象函数getLayoutId,来获得这个布局的资源id。

      然后在initViews中的参数holder中,利用holder.get(R.id.view_pager)就能访问到这个ViewPager。

    5.5.初始化view,注意是在initDatas调用之后,因为有了数据,才能填充到真实的视图。这里可以setTilte()来设置标题。

      然后就是一些缓存处理了。

    5.6.真正的图片要加载处理,必须要有一个adapter,所以这里new了一个PagerAdapter,实现了4个必须实现的方法。主要

      逻辑在instantiateItem(ViewGroup container,int position)中处理,比如开启photoView缩放功能。以及缓存处理。

    5.7.initViews函数执行结束之前一定要设置这个viewPager当前的页面,调用setCurrentItem(int)即可。

      这个int其实是在父类中定义,因为这个是继承了父类,也可以直接调用父类中的变量。

    5.8.最后是一个图片缓存策略,因为是灰色的,也没有执行,可以也不是必须要实现的。但是要是不得不说Glide有点强大,

      特别是处理图片问题,图片缓存问题,图片加载问题。有空再深究一下。


    既然选择了,便不顾风雨兼程。Just follow yourself.
  • 相关阅读:
    技术学习博2
    正则匹配两个特定词组中间的内容的写法
    springboot mitre HTTP Proxy Servlet POST hang timeout 不执行的解决方案
    JAVA实现的HTTP反向代理 [smiley-http-proxy-servlet]学习
    get方式url传递map的写法,control传入map数据url格式&params[key]=value&params[key2]=value2
    angular7 学习笔记记录--1. TypeScript基础
    Operator '>' cannot be applied to types 'boolean' and 'number'?
    记录在linux环境redis如何重启版本5.x
    ElasticSearch (with docker )入门学习
    Dataway【实测】,让服务飞起来,让SpringBoot不再需要Controller、Service、DAO、Mapper
  • 原文地址:https://www.cnblogs.com/Jason-Jan/p/7843782.html
Copyright © 2011-2022 走看看