精华内容
下载资源
问答
  • 2021-03-01 09:57:05

    上代码:

    import java.text.SimpleDateFormat;

    import java.util.Date;

    public class DateUtil {

    /**

    * 生成随机时间

    *

    * @param beginDate

    * @param endDate

    * @return

    */

    public static Date randomDate(String beginDate, String endDate) {

    try {

    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

    Date start = format.parse(beginDate);// 构造开始日期

    Date end = format.parse(endDate);// 构造结束日期

    // getTime()表示返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。

    if (start.getTime() >= end.getTime()) {

    return null;

    }

    long date = random(start.getTime(), end.getTime());

    return new Date(date);

    } catch (Exception e) {

    e.printStackTrace();

    }

    return null;

    }

    public static long random(long begin, long end) {

    long rtn = begin + (long) (Math.random() * (end - begin));

    // 如果返回的是开始时间和结束时间,则递归调用本函数查找随机值

    if (rtn == begin || rtn == end) {

    return random(begin, end);

    }

    return rtn;

    }

    public static void main(String[] args) {

    Date randomDate = randomDate("2010-09-20", "2010-09-22");

    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    String result = format.format(randomDate);

    System.out.println(result);

    }

    }

    Java 获取一段时间内的每一天

    有时候我们会遇到一些业务场景,需要去获取一段时间内的每一天日期 public static List findDates(Date dBegin, Date dEnd) { L ...

    java中计算一段时间内白天的时间和夜晚的时间

    之前,采用拼接字符串的形式,不断地在Date类型和Long类型之间转换,实在是太过于麻烦,后来采取了这种思路:假设我们将22:00 ~ 10:00 视为夜间时间,则我们先计算出10:00 相对于当天的 ...

    java获取某段时间内的月份列表

    /**获取两个时间节点之间的月份列表**/ private static List getMonthBetween(String minDate, String maxDa ...

    PHP随机生成指定时间段的指定个数时间

    /** * 生成某个范围内的随机时间 * @param $begintime 起始时间 格式为 Y-m-d H:i:s * @param $endt ...

    java生成随机序列号

    1.java生成随机序列号 String deleteUuid = UUID.randomUUID().toString(); 引用Jar包 //java-uuid-generator-3.1.3.j ...

    Java得到的一周的最后一天的一段时间内

    Java得到的一周的最后一天的一段时间内 1.设计源代码 LastDayOfWeek.java: /** * @Title:LastDayOfWeek.java * @Package:com.you. ...

    [实例]JAVA生成字母+随机数字并生成文件

    package com.ishow.control.code; import java.io.*; import java.text.SimpleDateFormat; import java.uti ...

    随机推荐

    C#返回时间格式转换成 js 字符串

    在.net 中,调用 post 或者 get和后台通信时,如果有时间返回信息,后台返回的时间信息一般是这样格式:Thu Jul 9 23:14:53 UTC+0800 2015,那么要在前台显示就会有 ...

    C#如何加载程序运行目录外的程序集

    我们的应用程序部署的时候,目录结构一般不会只有运行程序的目录这一个,我们可能在运行目录下建子目录,也可能使用System32目录,也可能使用其它第三方的程序集..Net程序集 首先会在GAC中搜索相应 ...

    jsp页面在IE8下文本模式自动为“杂项(Quirks)”导致页面显示错位

    最近在修改网站的响应式的页面时,由于都是套样式页面,修改过程都是粘贴复制,导致了一些细节问题在IE8下暴露出来: 遇到的问题就是在在Chrome,火狐页面都正常,唯独在IE8下页面样式显示乱样了,但是 ...

    【转】VSync Count 垂直同步

    原文:http://blog.csdn.net/yesy10/article/details/7794556 Unity3D中新建一个场景空的时候,帧速率(FPS总是很低),大概在60~70之间.一直 ...

    前端基于easyui的mvc扩展(续)

    前端基于easyui的mvc扩展(续) 回顾及遗留问题 上一篇讲解了基于easyui的mvc扩展的基本实现,已经降低了在mvc内使用easyui的难度,但是仍然还有一些问题: 当我们要给生成的控件设置 ...

    Python内置函数(43)——min

    英文文档: min(iterable, *[, key, default]) min(arg1, arg2, *args[, key]) Return the smallest item in an ...

    从头到尾使用Geth的说明-1-安装

    Geth 1.安装https://github.com/ethereum/go-ethereum/wiki/Installation-Instructions-for-Mac 1.首先先安装Homeb ...

    总目录(Catalog)

    总目录(Catalog) C#高级编程(C# advanced programming) 1.并发编程(Concurrent programming)(8) ...... 数据结构与算法(Data s ...

    Android ListView理解之BaseAdapter

    ListView是Android开发过程中较为常见的组件之一,它将数据以列表的形式展现出来.一般而言,一个ListView由以下三个元素组 成: 1.View,用于展示列表,通常是一个xml所指定的. ...

    android - 调用系统分享功能分享图片

    step1: 编写分享代码, 将Uri的生成方式改为由FileProvider提供的临时授权路径,并且在intent中添加flag 注意:在Android7.0之后,调用系统分享,传入URI的时候可能 ...

    更多相关内容
  • ... ...想了解这些的请绕道,相信有很多优秀的文章会告诉你。...(此图片来源于网络,如有侵权,请联系删除! ) 开始使用Let's Encrypt项目 Let's Encrypt项目官网: https://letsencrypt.org/getting-started/ 推荐使
  • 虽然 Word 看起来很简单,但是想要真正精通却需要花费时间学习。很多人都认为自己掌握了 Word,所以平时对 Word 的学习并不上心,等到在写论文或是工作中写结案报告时,常常会遇到 Word 插入图片后只显示一半等多种...

    虽然 Word 看起来很简单,但是想要真正精通却需要花费时间学习。

    很多人都认为自己掌握了 Word,所以平时对 Word 的学习并不上心,等到在写论文或是工作中写结案报告时,常常会遇到 Word 插入图片后只显示一半等多种问题,才发现怎么有这么多问题都解决不了。

    150202466_1_20181222021235137

    今天生活君为大家介绍三个解决 Word 常见难题的方法,帮你在10分钟内迅速 get 新技能。

    150202466_2_20181222021235419

    插入图片后只显示一半

    150202466_3_20181222021235669

    这个问题很多人一定都遇到过,在 Word 中插入图片后,发现只显示一部分。这是因为图片的高度超过行距设置,使图片无法全部显示。

    解决的方法也很简单,只需要调整段落的行距就可以了。

    150202466_4_20181222021235794

    从网页复制的文字有底色

    150202466_5_20181222021235934

    在写文档的时候经常需要上网搜集一些相关信息,把内容放到文档中进行细致的说明。为了节省时间,大家都会选择把内容复制到文档中。

    但是这样做就会发现,从网上复制的文字有一层淡淡的底色,其实这是段落格式中的底纹。

    解决这个问题有两个方法。一是直接去除底纹,选择“开始——段落——底纹”,在“底纹”中选择“无颜色”即可把底纹去掉。

    150202466_6_2018122202123644

    二是如果不想保留原字体格式,直接点击“清除格式”按钮即可。

    段落后面有竖直向下的箭头

    150202466_7_20181222021236231

    大家会发现,从网上复制的文字内容,段落后面总有一个向下的箭头。其实这个是手动换行符,文本复制到 Word 就会这样。

    如果大家想把手动换行符换成回车样式的话,按住 Ctrl + H 打开“查找和替换对话框”,在查找框中输入“^|”,在替换框中输入“^p”,全部替换即可。

    150202466_8_20181222021236341

    肿么样,是不是感觉打开了新世界的大门呢?

    150202466_9_20181222021236465

    展开全文
  • Picasso 是 Square 公司出品的一款十分优秀的开源图片框架,也是目前 Android 开发中十分流行的一款图片加载框架。提到 Square 公司大家一定不会陌生,OkHttp、Retrofit、LeakCanary 等等 Android 开发者...

    img

    个人博客:https://blog.N0tExpectErr0r.cn

    小专栏:https://xiaozhuanlan.com/N0tExpectErr0r

    各位好久不见…已经一两个月没更新了…过完年一直在忙着学校的一些事情和项目上的事,就没太多时间写笔记,现在我回来啦。

    Picasso 是 Square 公司出品的一款十分优秀的开源图片框架,也是目前 Android 开发中十分流行的一款图片加载框架。提到 Square 公司大家一定不会陌生,OkHttp、Retrofit、LeakCanary 等等 Android 开发者十分熟悉的开源库都出自他们之手,个人认为他们公司的开源库都十分值得研究,今天就让我们来研究一下 Picasso 这款图片加载框架。

    Picasso 属于三大图片框架(Glide、Picasso、Fresco)之一。相比其他两个框架,它的特点是轻量,占用的体积更少,同时功能相对来说也比较完善。那么今天就来跟我一起分析一波 Picasso 这个图片选择框架的源码。

    此篇文章的源码解析基于 2.71828 版本。

    初始化

    以我的阅读源码的习惯,都是从使用的时候的入口开始入手,因此我们这里从 Picasso 类入手。旧版本的 Picasso 使用了 with 方法作为入口,而在新版本中 with 方法则被 get 方法所替代,并且不再需要传入 Context 参数。那么它是如何实现的呢?下面我们看到它的 get 方法:

    public static Picasso get() {
      if (singleton == null) {
        synchronized (Picasso.class) {
          if (singleton == null) {
            if (PicassoProvider.context == null) {
              throw new IllegalStateException("context == null");
            }
            singleton = new Builder(PicassoProvider.context).build();
          }
        }
      }
      return singleton;
    }
    

    可以看到,这里是一个单例类,而它的 Context 则由一个没有任何实现的 PicassoProvider 这个 ContentProvider 来提供,从而使用户不再需要传入一个 Context。

    @RestrictTo(LIBRARY)
    public final class PicassoProvider extends ContentProvider {
    
      @SuppressLint("StaticFieldLeak") static Context context;
    
      @Override public boolean onCreate() {
        context = getContext();
        return true;
      }
    	// ...省略 ContentProvider 的默认实现
    }
    

    之后,它调用了 Builder 的 build 方法返回了一个 Picasso 对象。我们先看到 Builder 的构造方法:

    public Builder(@NonNull Context context) {
      if (context == null) {
        throw new IllegalArgumentException("Context must not be null.");
      }
      this.context = context.getApplicationContext();
    }
    

    可以看到仅仅是判空并赋值。接着我们看看 build 方法:

    public Picasso build() {
      Context context = this.context;
      if (downloader == null) {
        downloader = new OkHttp3Downloader(context);
      }
      if (cache == null) {
        cache = new LruCache(context);
      }
      if (service == null) {
        service = new PicassoExecutorService();
      }
      if (transformer == null) {
        transformer = RequestTransformer.IDENTITY;
      }
      Stats stats = new Stats(cache);
      Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }
    

    build 方法中对 downloader、cache 等变量进行了初始化,同时返回了一个新的 Picasso 对象,前面的变量我们先不关心。先看到 Picasso 的构造方法:

    Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
        RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
        Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
      this.context = context;
      this.dispatcher = dispatcher;
      this.cache = cache;
      this.listener = listener;
      this.requestTransformer = requestTransformer;
      this.defaultBitmapConfig = defaultBitmapConfig;
      int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
      int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
      List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
      // ResourceRequestHandler needs to be the first in the list to avoid
      // forcing other RequestHandlers to perform null checks on request.uri
      // to cover the (request.resourceId != 0) case.
      allRequestHandlers.add(new ResourceRequestHandler(context));
      if (extraRequestHandlers != null) {
        allRequestHandlers.addAll(extraRequestHandlers);
      }
      allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
      allRequestHandlers.add(new MediaStoreRequestHandler(context));
      allRequestHandlers.add(new ContentStreamRequestHandler(context));
      allRequestHandlers.add(new AssetRequestHandler(context));
      allRequestHandlers.add(new FileRequestHandler(context));
      allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
      requestHandlers = Collections.unmodifiableList(allRequestHandlers);
      this.stats = stats;
      this.targetToAction = new WeakHashMap<>();
      this.targetToDeferredRequestCreator = new WeakHashMap<>();
      this.indicatorsEnabled = indicatorsEnabled;
      this.loggingEnabled = loggingEnabled;
      this.referenceQueue = new ReferenceQueue<>();
      this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
      this.cleanupThread.start();
    }
    

    可以看到,主要是对 requestHandlers 这个 List 进行初始化以及各个变量进行初始化。通过上面的几个名字可以看出来 RequestHandler 就是 Picasso 对各种类型的图片加载的抽象。通过实现 RequestHandler 接口可以实现不同的图片加载策略。

    创建请求

    之后我们调用了 load 方法并传入了具体的参数。它有许多重载,可以传入 Uri、String、File、resourceId 等等类型的数据。

    我们以 load(String) 为例:

    public RequestCreator load(@Nullable String path) {
      if (path == null) {
        return new RequestCreator(this, null, 0);
      }
      if (path.trim().length() == 0) {
        throw new IllegalArgumentException("Path must not be empty.");
      }
      return load(Uri.parse(path));
    }
    

    可以看到,它最终调用的还是 load(Uri) 方法。其实所有的其他重载最后都会指向 load(Uri) 方法,也就是说我们-各种形式的数据源最后都是以 Uri 的形式存在于 Picasso 中。我们下面看到 load(Uri):

    public RequestCreator load(@Nullable Uri uri) {
      return new RequestCreator(this, uri, 0);
    }
    

    它构造了一个 RequestCreator 并返回。接下来我们看到 RequestCreator 的构造方法:

    RequestCreator(Picasso picasso, Uri uri, int resourceId) {
      if (picasso.shutdown) {
        throw new IllegalStateException(
            "Picasso instance already shut down. Cannot submit new requests.");
      }
      this.picasso = picasso;
      this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
    }
    

    它调用了 Request.Builder 的构造方法来为 data 进行赋值,我们看到这个构造方法:

    Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
      this.uri = uri;
      this.resourceId = resourceId;
      this.config = bitmapConfig;
    }
    

    可以看到,这里主要是对 Bitmap.Config 等属性进行设置。

    配置加载属性

    在我们创建了 RequestCreator 后,可以调用它的 placeholder、error 等等方法为本次加载设置占位图、错误图等等各种属性的设置,下面我们以 placeholder(int) 方法举例:

    public RequestCreator placeholder(@DrawableRes int placeholderResId) {
      if (!setPlaceholder) {
        throw new IllegalStateException("Already explicitly declared as no placeholder.");
      }
      if (placeholderResId == 0) {
        throw new IllegalArgumentException("Placeholder image resource invalid.");
      }
      if (placeholderDrawable != null) {
        throw new IllegalStateException("Placeholder image already set.");
      }
      this.placeholderResId = placeholderResId;
      return this;
    }
    

    其实这里就是为 RequestCreator 中的这些属性赋值。

    那么所有通过 RequestCreator 设定的属性都是放在 RequestCreator 这个类中的么?

    其实不是的,与加载过程有关的属性是放在 RequestCreator 中的,而与图片相关的属性则是放在 Request.Builder 中。

    可能看到这里有点乱,大概解释一下。

    比如 placeholder、error、memoryPolicy 这些属性就是与加载过程有关而与图片无关的

    而比如 resize、centerCrop 这些就是与图片的显示效果有关的属性,也就是图片相关属性。

    我们以 resize 为例来看看整体流程:

    public RequestCreator resize(int targetWidth, int targetHeight) {
      data.resize(targetWidth, targetHeight);
      return this;
    }
    

    我们看到 Request.Builder 中的 resize 方法:

    public Builder resize(@Px int targetWidth, @Px int targetHeight) {
      if (targetWidth < 0) {
        throw new IllegalArgumentException("Width must be positive number or 0.");
      }
      if (targetHeight < 0) {
        throw new IllegalArgumentException("Height must be positive number or 0.");
      }
      if (targetHeight == 0 && targetWidth == 0) {
        throw new IllegalArgumentException("At least one dimension has to be positive number.");
      }
      this.targetWidth = targetWidth;
      this.targetHeight = targetHeight;
      return this;
    }
    

    这里就是将 Request.Builder 中的一些属性进行了赋值。

    加载图片

    当属性都设定完后,我们便可以调用 into 方法来加载图片,我们看到 into(ImageView):

    public void into(ImageView target) {
      into(target, null);
    }
    

    它调用了 into(ImageView, Callback):

    public void into(ImageView target, Callback callback) {
      long started = System.nanoTime();
      // 1
      // 检查是否在主线程
      checkMain();	
      if (target == null) {
        throw new IllegalArgumentException("Target must not be null.");
      }
      if (!data.hasImage()) {
      	// 之前设置的 uri 是否有数据(实际上也是判空)
        picasso.cancelRequest(target);
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        return;
      }
      // 2
      if (deferred) {
      	// 是否自适应 Target 宽高
        if (data.hasSize()) {
          throw new IllegalStateException("Fit cannot be used with resize.");
        }
        int width = target.getWidth();
        int height = target.getHeight();
        if (width == 0 || height == 0) {
          if (setPlaceholder) {
            setPlaceholder(target, getPlaceholderDrawable());
          }
          picasso.defer(target, new DeferredRequestCreator(this, target, callback));
          return;
        }
        data.resize(width, height);
      }
      // 3
      Request request = createRequest(started);
      String requestKey = createKey(request);
      // 4
      if (shouldReadFromMemoryCache(memoryPolicy)) {
      	// 从内存缓存中获取图片
        Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
        if (bitmap != null) {
          // 找到缓存的图片
          picasso.cancelRequest(target);
          setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
          if (picasso.loggingEnabled) {
            log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
          }
          if (callback != null) {
            callback.onSuccess();
          }
          return;
        }
      }
      // 5
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      // 6
      Action action =
          new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
              errorDrawable, requestKey, tag, callback, noFade);
      picasso.enqueueAndSubmit(action);
    }
    

    这里代码比较长,我们慢慢分析,先看看整体大体流程。

    首先在注释 1 处进行了一系列判断操作,具体可看注释

    之后在注释 2 处,是 fit() 的具体实现。如果外部调用了 fit 使图片自适应 target 的大小,则获取 target 的大小并调用 resize 方法进行设置。这里要特别注意的是如果宽高为 0 则说明 ImageView 的尺寸还没有获取到,此时会延时该图片请求直到获取到 ImageView 的宽高。

    之后 3 处构建了一个 Request,并调用 createKey 方法由该 Request 及其各种信息构建了一个 String 类型的 key。

    之后在注释 4 处,在使用内存缓存策略的情况下,先调用 quickMemoryCacheCheck 方法获取到了内存缓存中的 BitMap,如果找到则调用 setBitmap 方法将图片应用到 target 中。

    然后在注释 5 处,如果内存没有缓存,且设置了占位图,则给它添加占位图。

    最后在注释 6 处,构造了一个 Action 对象然后调用了 picasso 的 enqueueAndSubmit 进行网络请求。

    Request 的创建

    首先,我们看看 Request 是如何创建的,看到 createRequest 方法:

    private Request createRequest(long started) {
      int id = nextId.getAndIncrement();
      // 1
      Request request = data.build();
      request.id = id;
      request.started = started;
      boolean loggingEnabled = picasso.loggingEnabled;
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());
      }
      // 2
      Request transformed = picasso.transformRequest(request);
      if (transformed != request) {
      	// 3
        // If the request was changed, copy over the id and timestamp from the orig
        transformed.id = id;
        transformed.started = started;
        if (loggingEnabled) {
          log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed)
        }
      }
      return transformed;
    }
    

    可以看到,这里首先在注释 1 处调用了 Request.Builder 的 build 方法创建了 Request,之后在注释 2 处调用了 picasso 的 transformRequest 方法对 Request 进行转换。

    获取 Request

    我们先看看 Request.Builder 的 build 方法:

    public Request build() {
      if (centerInside && centerCrop) {
        throw new IllegalStateException("Center crop and center inside can not be used together.");
      }
      if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {
        throw new IllegalStateException(
            "Center crop requires calling resize with positive width and height.");
      }
      if (centerInside && (targetWidth == 0 && targetHeight == 0)) {
        throw new IllegalStateException(
            "Center inside requires calling resize with positive width and height.");
      }
      if (priority == null) {
        priority = Priority.NORMAL;
      }
      return new Request(uri, resourceId, stableKey, transformations, targetWidth, targetHeight,
          centerCrop, centerInside, centerCropGravity, onlyScaleDown, rotationDegrees,
          rotationPivotX, rotationPivotY, hasRotationPivot, purgeable, config, priority);
    }
    

    这里就是创建 Request 对象并将各种 Request.Builder 中的属性传递给这个 Request 对象。

    转换 Request

    然后我们再看看 picasso 的 transformRequest 方法:

    Request transformRequest(Request request) {
      Request transformed = requestTransformer.transformRequest(request);
      if (transformed == null) {
        throw new IllegalStateException("Request transformer "
            + requestTransformer.getClass().getCanonicalName()
            + " returned null for "
            + request);
      }
      return transformed;
    }
    

    这里调用了 requestTransformer 的 transformRequest 方法来进行转换。而这个 requestTrasformer 则是之前在 Picasso.Builder 中的 build 方法中初始化给 transformer 的 RequestTransformer.IDENTITY:

    if (transformer == null) {
      transformer = RequestTransformer.IDENTITY;
    }
    

    我们看看它的 transformRequest 的实现:

    RequestTransformer IDENTITY = new RequestTransformer() {
      @Override public Request transformRequest(Request request) {
        return request;
      }
    };
    

    可以看到这里是返回了原始的 Request。

    既然都是返回默认 Request,为什么 Picasso 还要在创建的时候添加这一步 transform 的过程呢?

    其实这个 transformer 我们是可以通过 Builder 的 requestTransformer 方法来进行设置的。也就是说这里主要是提供给用户对 Request 进行一些特殊处理的渠道,使得我们可以对图片加载的过程进行一定的扩展与定制。这种设计是值得我们去学习的。

    之后我们回到 Request 创建的部分,可以看到这里如果对 Request 进行了修改,在注释 3 处会将原 Request 的 id 和 started 赋值过去,从而防止用户对它们进行修改。

    key 的生成

    我们再来看看 Request 的 key 是如何生成的:

    static String createKey(Request data, StringBuilder builder) {
      if (data.stableKey != null) {
        builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
        builder.append(data.stableKey);
      } else if (data.uri != null) {
        String path = data.uri.toString();
        builder.ensureCapacity(path.length() + KEY_PADDING);
        builder.append(path);
      } else {
        builder.ensureCapacity(KEY_PADDING);
        builder.append(data.resourceId);
      }
      builder.append(KEY_SEPARATOR);
      if (data.rotationDegrees != 0) {
        builder.append("rotation:").append(data.rotationDegrees);
        if (data.hasRotationPivot) {
          builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
        }
        builder.append(KEY_SEPARATOR);
      }
      if (data.hasSize()) {
        builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
        builder.append(KEY_SEPARATOR);
      }
      if (data.centerCrop) {
        builder.append("centerCrop:").append(data.centerCropGravity).append(KEY_SEPARATOR);
      } else if (data.centerInside) {
        builder.append("centerInside").append(KEY_SEPARATOR);
      }
      if (data.transformations != null) {
        //noinspection ForLoopReplaceableByForEach
        for (int i = 0, count = data.transformations.size(); i < count; i++) {
          builder.append(data.transformations.get(i).key());
          builder.append(KEY_SEPARATOR);
        }
      }
      return builder.toString();
    }
    

    其实这里就是用一个 StringBuilder 构造了一个 String,将 Request 中的各类信息都存放于 key 中。这个 key 其实就是用于内存缓存中的 key。

    图片的加载

    我们先不去查看内存缓存的部分,留到后面来讲解,我们先看看图片是如何从网络加载的。先看到 into 方法的下面这两句:

    Action action =
        new FetchAction(picasso, request, memoryPolicy, networkPolicy, tag, key, callback);
    picasso.submit(action);
    

    构造 Action

    我们先看到 FetchAction 的构造方法:

    FetchAction(Picasso picasso, Request data, int memoryPolicy, int networkPolicy, Object tag,
        String key, Callback callback) {
      super(picasso, null, data, memoryPolicy, networkPolicy, 0, null, key, tag, false);
      this.target = new Object();
      this.callback = callback;
    }
    

    调用了父类的构造方法:

    Action(Picasso picasso, T target, Request request, int memoryPolicy, int networkPolicy,
        int errorResId, Drawable errorDrawable, String key, Object tag, boolean noFade) {
      this.picasso = picasso;
      this.request = request;
      this.target =
          target == null ? null : new RequestWeakReference<>(this, target, picasso.referenceQueue);
      this.memoryPolicy = memoryPolicy;
      this.networkPolicy = networkPolicy;
      this.noFade = noFade;
      this.errorResId = errorResId;
      this.errorDrawable = errorDrawable;
      this.key = key;
      this.tag = (tag != null ? tag : this);
    }
    

    可以看出来,Action 类实际上就是一个携带了需要的信息的类。

    分发 Action

    接着,调用了 picasso 的 submit 方法:

    void submit(Action action) {
      dispatcher.dispatchSubmit(action);
    }
    

    这里调用了 dispatcher 的 dispatchSubmit 方法:

    void dispatchSubmit(Action action) {
      handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
    }
    

    这里用到了一个 DispatcherHandler 类的对象调用 sendMessage 方法发送一条信息。这里的 DispatcherHandler 的作用主要是根据不同的调用将 Action 分发到不同的方法中。

    下面我们看到 DispatcherHandler 的实现,它是 Dispatcher 的一个内部类:

    @Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        case REQUEST_SUBMIT: {
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;
        }
        case REQUEST_CANCEL: {
          Action action = (Action) msg.obj;
          dispatcher.performCancel(action);
          break;
        }
        case TAG_PAUSE: {
          Object tag = msg.obj;
          dispatcher.performPauseTag(tag);
          break;
        }
        case TAG_RESUME: {
          Object tag = msg.obj;
          dispatcher.performResumeTag(tag);
          break;
        }
        case HUNTER_COMPLETE: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performComplete(hunter);
          break;
        }
        case HUNTER_RETRY: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performRetry(hunter);
          break;
        }
        case HUNTER_DECODE_FAILED: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performError(hunter, false);
          break;
        }
        case HUNTER_DELAY_NEXT_BATCH: {
          dispatcher.performBatchComplete();
          break;
        }
        case NETWORK_STATE_CHANGE: {
          NetworkInfo info = (NetworkInfo) msg.obj;
          dispatcher.performNetworkStateChange(info);
          break;
        }
        case AIRPLANE_MODE_CHANGE: {
          dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);
          break;
        }
        default:
          Picasso.HANDLER.post(new Runnable() {
            @Override public void run() {
              throw new AssertionError("Unknown handler message received: " + msg.what);
            }
          });
      }
    }
    

    这里根据不同的 Message 调用了不同的方法,我们的 submit 方法调用了 Dispatcher 中的 performSubmit 方法:

    void performSubmit(Action action) {
      performSubmit(action, true);
    }
    

    它调用了 performSubmit(Action, boolean) 方法:

    void performSubmit(Action action, boolean dismissFailed) {
      if (pausedTags.contains(action.getTag())) {
        pausedActions.put(action.getTarget(), action);
        if (action.getPicasso().loggingEnabled) {
          log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
              "because tag '" + action.getTag() + "' is paused");
        }
        return;
      }
      // 1
      BitmapHunter hunter = hunterMap.get(action.getKey());
      if (hunter != null) {
        hunter.attach(action);
        return;
      }
      
      // 2
      if (service.isShutdown()) {
        if (action.getPicasso().loggingEnabled) {
          log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down")
        }
        return;
      }
      // 3
      hunter = forRequest(action.getPicasso(), this, cache, stats, action);
      hunter.future = service.submit(hunter);
      hunterMap.put(action.getKey(), hunter);
      if (dismissFailed) {
        failedActions.remove(action.getTarget());
      }
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
      }
    }
    

    首先,在注释 1 处根据 Action 获取到了其对应的 BitmapHunter。

    之后在注释 2 处检查 service 是否被杀掉。

    然后在注释 3 处,调用了 forRequest 获取到了 Action 对应的 BitmapHunter,然后调用了 service 的 submit 方法。

    之后将该 action 与 BitmapHunter 放入了 hunterMap 中。

    BitmapHunter 的获取

    我们看一下前面的步骤中 BitmapHunter 是如何获取的,来到 forRequest方法:

    static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
      Request request = action.getRequest();
      List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
      // Index-based loop to avoid allocating an iterator.
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, count = requestHandlers.size(); i < count; i++) {
        RequestHandler requestHandler = requestHandlers.get(i);
        if (requestHandler.canHandleRequest(request)) {
          return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
        }
      }
      return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
    }
    

    这里主要是依次遍历各个 RequestHandler,找到可以处理该类 Request 的 Handler,并构建 BitmapHunter。

    我们先看看 RequestHunter 是如何判断能否处理该类 Request 的,我们以 NetworkRequestHandler 举例:

    @Override public boolean canHandleRequest(Request data) {
      String scheme = data.uri.getScheme();
      return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
    }
    

    可以看到,它是通过判断 uri 的 scheme 来判断能否处理该类 Request 的。

    我们接着看到 BitmapHunter 的构造函数:

    BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action, RequestHandler requestHandler) {
      this.sequence = SEQUENCE_GENERATOR.incrementAndGet();
      this.picasso = picasso;
      this.dispatcher = dispatcher;
      this.cache = cache;
      this.stats = stats;
      this.action = action;
      this.key = action.getKey();
      this.data = action.getRequest();
      this.priority = action.getPriority();
      this.memoryPolicy = action.getMemoryPolicy();
      this.networkPolicy = action.getNetworkPolicy();
      this.requestHandler = requestHandler;
      this.retryCount = requestHandler.getRetryCount();
    }
    

    可以看到,这里主要是各种变量的赋值。

    接着我们看到 service 的 submit 方法,这里的 service 是 PicassoExecutorService:

    @Override
    public Future<?> submit(Runnable task) {
      PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
      execute(ftask);
      return ftask;
    }
    

    这里构建了一个 PicassoFutureTask,然后调用了 execute 方法

    我们先看看 PicassoFutureTask 的构造方法:

    PicassoFutureTask(BitmapHunter hunter) {
      super(hunter, null);
      this.hunter = hunter;
    }
    

    PicassoFutureTask 是 FutureTask 的子类,这里主要是变量的赋值。

    图片资源的获取

    接着我们看到 execute 方法,这里其实是调用了 Java 自带的 ThreadPoolExecutor 的 execute 方法。同时这里也说明了这里是一个异步的过程。

    其实 BitmapHunter 是一个 Runnable,当调用了 execute 方法后便会执行它的 run 方法。我们可以看到它的 run 方法:

    @Override public void run() {
      try {
        updateThreadName(data);
        if (picasso.loggingEnabled) {
          log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
        }
        result = hunt();
        if (result == null) {
          dispatcher.dispatchFailed(this);
        } else {
          dispatcher.dispatchComplete(this);
        }
      }
      // 省略后面的 catch
    }
    

    这里调用了 hunt 方法获取到了结果 Bitmap,同时在后面根据不同的结果通过 dispatcher 进行结果的处理:

    Bitmap hunt() throws IOException {
      Bitmap bitmap = null;
      // 1
      if (shouldReadFromMemoryCache(memoryPolicy)) {
        bitmap = cache.get(key);
        if (bitmap != null) {
          stats.dispatchCacheHit();
          loadedFrom = MEMORY;
          if (picasso.loggingEnabled) {
            log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
          }
          return bitmap;
        }
      }
      // 2
      networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
      // 3
      RequestHandler.Result result = requestHandler.load(data, networkPolicy);
      if (result != null) {
        loadedFrom = result.getLoadedFrom();
        exifOrientation = result.getExifOrientation();
        bitmap = result.getBitmap();
        // If there was no Bitmap then we need to decode it from the stream.
        // 4
        if (bitmap == null) {
          Source source = result.getSource();
          try {
            bitmap = decodeStream(source, data);
          } finally {
            try {
              //noinspection ConstantConditions If bitmap is null then source is guranteed non-null.
              source.close();
            } catch (IOException ignored) {
            }
          }
        }
      }
      
      // 5
      if (bitmap != null) {
        if (picasso.loggingEnabled) {
          log(OWNER_HUNTER, VERB_DECODED, data.logId());
        }
        stats.dispatchBitmapDecoded(bitmap);
        if (data.needsTransformation() || exifOrientation != 0) {
          synchronized (DECODE_LOCK) {
     				// 6
            if (data.needsMatrixTransform() || exifOrientation != 0) {
              bitmap = transformResult(data, bitmap, exifOrientation);
              if (picasso.loggingEnabled) {
                log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
              }
            }
            // 7
            if (data.hasCustomTransformations()) {
              bitmap = applyCustomTransformations(data.transformations, bitmap);
              if (picasso.loggingEnabled) {
                log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
              }
            }
          }
          if (bitmap != null) {
            stats.dispatchBitmapTransformed(bitmap);
          }
        }
      }
      return bitmap;
    }
    

    这里代码很长,我们慢慢分析:

    首先在注释 1 处尝试从内存通过 key 获取对应 bitmap,若获取到则直接返回。

    之后在注释 2 处,根据 requestHandler 中的 retryCount 来判断是否是网络请求,从而获取不同的 networkPolicy。若 retryCount 为 0 则为离线策略。

    之后在注释 3 处,通过 requestHandler 的 load 方法进行数据的加载,若数据加载成功则进行一些变量的赋值,并获取 bitmap。

    若 bitmap 为空则说明我们需要在注释4处将其从流中 decode 出来。

    之后在注释 5 处就是 Picasso 的加载过程中支持用户对图片进行定制后再应用的具体实现了。这里首先判断是否需要 transform。

    在注释 6 处判断如果需要进行矩阵变换(旋转,放大缩小等),则调用 transformResult 方法进行变换。

    在注释 7 处判断如果有自定义变换,则调用 applyCustomTransformations 进行自定义变换。

    这里的自定义变换比较类似前面的自定义 Request 转换,用户可以在外部自定义 Transformation,并通过 RequestCreator 的 transform 方法传入,这样就可以在图片应用前对 Bitmap 进行一些自定义 (如高斯模糊等)后再应用于 target。这种设计是我们值得学习的。

    RequestHandler 的实现

    下面我们以网络图片对应的 NetworkRequestHandler 为例看看它们的实现,其他的子类可以自己去了解。让我们看到它的 load 方法:

    @Override public Result load(Request request, int networkPolicy) throws IOException {
    	// 1 
      okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
      // 2
      Response response = downloader.load(downloaderRequest);
      ResponseBody body = response.body();
      if (!response.isSuccessful()) {
        body.close();
        throw new ResponseException(response.code(), request.networkPolicy);
      }
      // Cache response is only null when the response comes fully from the network. Both completely
      // cached and conditionally cached responses will have a non-null cache response.
      Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
      // Sometimes response content length is zero when requests are being replayed. Haven't found
      // root cause to this but retrying the request seems safe to do so.
      if (loadedFrom == DISK && body.contentLength() == 0) {
        body.close();
        throw new ContentLengthException("Received response with 0 content-length header.");
      }
      if (loadedFrom == NETWORK && body.contentLength() > 0) {
        stats.dispatchDownloadFinished(body.contentLength());
      }
      return new Result(body.source(), loadedFrom);
    }
    

    可以看到,这里是通过 OkHttp3 来实现的图片的加载。

    首先调用 createRequest 方法创建了 OkHttp 的 Request。然后通过自己实现的 OkHttp3Downloader 的 load 方法来实现对这个 Request 的下载请求。

    之后根据缓存的相应是否是空判断数据的来源是从本地还是网络。

    最终构造了一个 Result 并返回。

    OkHttp3.Request 的 创建

    我们先看看如何将 Request 转换为 OkHttp3.Request。让我们看到 createRequest 方法:

    private static okhttp3.Request createRequest(Request request, int networkPolicy) {
      CacheControl cacheControl = null;
      if (networkPolicy != 0) {
        if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
          cacheControl = CacheControl.FORCE_CACHE;
        } else {
          CacheControl.Builder builder = new CacheControl.Builder();
          if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
            builder.noCache();
          }
          if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
            builder.noStore();
          }
          cacheControl = builder.build();
        }
      }
      okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(request.uri.toString());
      if (cacheControl != null) {
        builder.cacheControl(cacheControl);
      }
      return builder.build();
    }
    

    可以看到,首先根据 Request 和 NetworkPolicy 的参数设置缓存的各种参数,之后调用 okhttp3.Request.Builder 的构造函数并传入 uri 创建了 Request。

    OkHttp3 数据的获取

    之后我们看到 OkHttp3Downloader 的 load 方法,看看数据获取是如何实现的:

    @NonNull @Override public Response load(@NonNull Request request) throws IOException {
      return client.newCall(request).execute();
    }
    

    其实就是调用 OkHttpClient 的 newCall 方法并调用 execute 获取一个 Response。

    结果的处理

    前面提到,在 BitmapHunter 的 run 方法中根据 hunt() 返回的结果成功与否调用了 dispatcher 的不同方法来进行的结果处理,让我们看看是如何处理的

    if (result == null) {
       dispatcher.dispatchFailed(this);
      } else {
       dispatcher.dispatchComplete(this);
    }
    

    我们先看到 dispatchComplete 方法,它最终通过 handler 调用到了 performComplete 方法中:

    void performComplete(BitmapHunter hunter) {
      if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
        cache.set(hunter.getKey(), hunter.getResult());
      }
      hunterMap.remove(hunter.getKey());
      batch(hunter);
      if (hunter.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
      }
    }
    

    可以看到,这里如果获取到了结果,且需要内存缓存,则将其放入内存缓存。然后将这个 BitmapHunter 从 Map 中删除。

    之后我们看到 dispatchFailed 方法,它最终通过 handler 调用到了 performError 方法:

    void performError(BitmapHunter hunter, boolean willReplay) {
      if (hunter.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter),
            "for error" + (willReplay ? " (will replay)" : ""));
      }
      hunterMap.remove(hunter.getKey());
      batch(hunter);
    }
    

    这里它将 BitmapHunter 从 Map 中移除,然后就没有进行其他处理了。

    内存缓存

    为了优化流量消耗,Picasso 加入了内存缓存机制,下面我们来看看 Picasso 内存缓存的实现。

    就像其他部分一样,它的内存缓存也考虑到了扩展性,给了用户自己实现的接口。

    我们可以调用 Picasso 类的 memoryCache 方法为其设置 Cache 接口的子类,从而实现自己的内存缓存。

    若用户不传入指定缓存,则默认使用 Picasso 自己实现的 LruCache。

    具体的 LruCache 的设计这里不深入讲解,有兴趣的各位可以去了解一下 LRU 算法,以后可能可以专门开一篇博客讲讲 LRU 算法。

    Dispatcher 设计

    其实从前面的讲解中,你会发现,其实如图片的加载请求,缓存命中等等事件都是由一个叫 Dispatcher 的类分发的,它内部由 Handler 实现,负责将请求封装,并按优先级排序,之后按照类型分发。

    这种设计也很值得我们学习,它作为一个分发中心管理我们的各类请求。使得我们的设计更为清晰,也使得库更容易维护。

    线程池设计

    之前没有提到的就是 Picasso 对线程池也有一些优化,它自己实现了一个 PicassoExecutorService 类,它可以根据当前的网络状态,采用不同的线程池数量,从而使得网络不会过于拥塞。

    具体可以看下面这个方法:

    void adjustThreadCount(NetworkInfo info) {
      if (info == null || !info.isConnectedOrConnecting()) {
        setThreadCount(DEFAULT_THREAD_COUNT);
        return;
      }
      switch (info.getType()) {
        case ConnectivityManager.TYPE_WIFI:
        case ConnectivityManager.TYPE_WIMAX:
        case ConnectivityManager.TYPE_ETHERNET:
          setThreadCount(4);
          break;
        case ConnectivityManager.TYPE_MOBILE:
          switch (info.getSubtype()) {
            case TelephonyManager.NETWORK_TYPE_LTE:  // 4G
            case TelephonyManager.NETWORK_TYPE_HSPAP:
            case TelephonyManager.NETWORK_TYPE_EHRPD:
              setThreadCount(3);
              break;
            case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
            case TelephonyManager.NETWORK_TYPE_CDMA:
            case TelephonyManager.NETWORK_TYPE_EVDO_0:
            case TelephonyManager.NETWORK_TYPE_EVDO_A:
            case TelephonyManager.NETWORK_TYPE_EVDO_B:
              setThreadCount(2);
              break;
            case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
            case TelephonyManager.NETWORK_TYPE_EDGE:
              setThreadCount(1);
              break;
            default:
              setThreadCount(DEFAULT_THREAD_COUNT);
          }
          break;
        default:
          setThreadCount(DEFAULT_THREAD_COUNT);
      }
    }
    

    可以看到,线程池最大线程个数如下:

    • 在 WIFI 网络下,采用最多 4 个线程的线程池
    • 在 4G 网络下,采用最多 3 个线程的线程池
    • 在 3G 网络下,采用最多 2 个线程的线程池
    • 在 2G 网络下,采用最多 1 个线程的线程池

    总结

    Picasso 是一个非常值得我们学习的轻量级图片加载库,它采用 OkHttp3 来加载网络图片,并使用了二级内存缓存来提高加载速度。它的 Dispatcher 思想以及对外部的扩展开放的思想十分值得我们学习,这次源码的阅读还是给了我很大的启发的。

    当然,由于篇幅有限,这篇文章并没有包含 Picasso 的方方面面,它的代码中还有如下的一些点在本文还没有分析,读者们有兴趣的可以从下面的点去研究这个库:

    • 图片加载的暂停与取消
    • 图片的变换实现
    • 请求的优先级
    • 对整体的监控
    • 本地资源的加载
    展开全文
  • 然后组织好值后,触发组件的时间input this.$emit('input', rtn)。 methods: { handleInput(value) { var rtn = { ...this.answer, content: value, state: value >= 0 } console.log('single handleInput:', rtn) ...

    对于学生党来说,最常见的莫过于试卷考试题,调查问卷测试题等等,有没有一款论文是管理考试题库的?对于新手刚入门的同学来说,仔细看本文,保证你看完后可以自己手撸代码。

    1、试卷题

    试卷题型有很多种,常见的有单选、多选、填空、图片题等等,例如下图的问卷调查题。
    在这里插入图片描述

    2 题型组件定义

    这么多题型,其界面和操作形式各有异同,因此我们可以简单的按照题型进行设计组件。

    如下组织形式,我们先建立WQuestionItem 文件夹,然后添加各型号的题型组件:

    • ImageSingleItem : 图片单选题
    • MultiItem: 多选题
    • SingleItem: 单选题
    • TextItem: 填空题
    • NumItem: 数字题

    在这里插入图片描述

    2.1 设计单选题型组件

    万事开头难,如果我们能顺利的设计好单选题型,那么多选题型可能仅仅是选择和结果不同而已。

    因此在设计单选题组件时,我们需要费点力气。

    好了,看看设计的组件类图。
    在这里插入图片描述
    这里单选题型组件作为一个响应输入,返回结果的组件,最最适合实现其v-model的双向绑定行为。我们都试过ElementUI的各个vue组件,那个设计的溜啊,这里我们也借鉴以下。

    vue官方解释:

    一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件

    但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。利用model 选项可以用来避免这样的冲突。

    2.1.1 模板设计

    我们采用ElementUI来作为主要的输出UI。在布局上使用el-card,刚好把试题的标题放在header区,把选项放在数据区。

    <template>
      <div>
        <el-card class="box-card" :shadow="shadow" :body-style="bodyStyle">
          <div slot="header" class="clearfix">
            <el-row type="flex" justify="space-between">
              <el-col :span="20">
                <h3>{{ question.title }}</h3>
              </el-col>
              <el-col :span="4" style="width:300px;text-align:right;">
              </el-col>
            </el-row>
          </div>
          <el-form
            ref="form"
            :model="answer"
            label-width="20px"
            label-position="top"
            size="medium"
            class="question"
          >
            <template>
              <el-form-item>
                <el-radio-group ref="rg" :value="answer.content" @input="handleInput">
                  <el-col
                    v-for="(item, index) in question.items"
                    :key="index"
                    :class="className"
                    style="margin-bottom:20px;"
                    @mouseover.native="hover($event)"
                    @mouseout.native="removeClass($event)"
                  >
                    <el-radio :label="item.index">{{ item.content }}</el-radio>
                  </el-col>
                </el-radio-group>
              </el-form-item>
            </template>
          </el-form>
        </el-card>
      </div>
    </template>
    

    这里使用
    @mouseover.native="hover($event)" @mouseout.native="removeClass($event)"
    完成鼠标滑过选项的效果。

    2.1.2 组件代码实现1

    可以预见的是,组件内必然有部分代码需要公用,因此我们设计了混合类,来复用功能。

    export const CommonItem = {
      data() {
        return {
          className: '',
          activeName: 0,
        }
      },
      created() {
        // console.log('这是混入对象的created')
      },
      methods: {
        hover(event) {
          event.currentTarget.className = 'bold'
        },
        removeClass(event) {
          event.currentTarget.className = ''
        },
      },
      computed: {
        shadow() {
          return this.question.child ? 'never' : 'always'
        },
        bodyStyle() {
          return this.question.child ? 'min-height:320px ;background-color: transparent'
            : 'min-height:320px ;background: -webkit-linear-gradient(top,rgba(234, 240, 184, 0.212),rgba(224, 221, 172, 0.507),rgb(234, 240, 184)) no-repeat;'
        },
      },
    }
    

    2.1.3 组件代码实现2

    混合类作为基础打底,也可以随时把公用代码提取到混合类里。
    现在该到了单选组件实现的代码了。

    
    // 引用混合类
    import { CommonItem } from './mixins'
    export default {
      name: 'SingleItem',
      mixins: [CommonItem],
      //重定义model名,我们接收到answer
      model: { 
        prop: 'answer',
        event: 'input'
      },
      props: {
        // 题
        question: {
          type: Object,
          required: true,
          default: () => {
            return {}
          }
        },
        answer: {
          type: Object
        },
      },
    }
    

    再设计方法,主要处理输入事件。
    注意:选项的值放在 answer.content。然后组织好值后,触发组件的时间input this.$emit('input', rtn)

    methods: {
        handleInput(value) {
          var rtn = {
            ...this.answer,
            content: value,
            state: value >= 0
          }
          console.log('single handleInput:', rtn)
          this.$emit('input', rtn)
        },
        handleKeyDown(e) {
          // console.log('key:',e.keyCode)
          if (e.keyCode > 48 && e.keyCode <= 57) {
            // 1 答案0
            var index = (e.keyCode - 49)
            if (this.question.items.length > index && index >= 0) {
              var ans = index.toString()
              this.handleInput(ans)
            }
            e.preventDefault()
          }
        },
        handleKeyUp(e) {
    
        },    
      }
    

    2.2 填空题组件

    既然已经搞定了单项选择题,那么我们就开始填空题组件,应为它比较简单,柿子先从软的捏。

    复制单选择题组件,修改为TextItem.vue.
    替换模板中的el-form:

    <el-form
            ref="form"
            :model="answer"
            label-width="20px"
            label-position="top"
            size="medium"
            class="question"
          >
            <template>
              <el-form-item label="">
                <el-input
                  type="textarea"
                  :value="textVal"
                  @input="handleInput"
                />
              </el-form-item>
            </template>
          </el-form>
    

    2.2.1 定义组件代码

    增加data:

    data() {
        return {
          textVal: Array.isArray(this.answer.content) ? '' : this.answer.content
        }
      },
    

    2.2.2 处理输入

    handleInput(value) {
     var rtn = {
         ...this.answer,
         content: value,
         state: value.length > 0
       }
    
       console.log('text handleInput:', rtn)
       this.$emit('input', rtn)
     },
    

    2.3 多选题组件

    利用Ctrl+C、Ctrl+V大法,修改el-form代码:

    <el-form
            ref="form"
            :model="answer"
            label-width="20px"
            label-position="top"
            size="medium"
            class="question"
          >
       <template>
         <el-form-item>
           <el-checkbox-group :value="answer.content" @input="handleInput">
             <el-col
               v-for="(item, index) in question.items"
               :key="index"
               style="margin-bottom: 20px"
               @mouseover.native="hover($event)"
               @mouseout.native="removeClass($event)"
             >
               <el-checkbox
                 :label="item.index"
               >【{{ item.index }}】、{{ item.content }}</el-checkbox>
             </el-col>
           </el-checkbox-group>
         </el-form-item>
       </template>
     </el-form>
    

    这里仅需要处理输入事件即可,我们返回一个数组作为多选题的答案。

    handleInput(value) {
          var rtn = {
            ...this.answer,
            content: [],
            state: value.length > 0
          }
          var arr = value || []
          arr.forEach(item => {
            if (item != '' && item != '-1') {
              rtn.content.push(item)
            }
          })
    
          console.log('multi handleInput:', rtn)
          this.$emit('input', rtn)
        }
    

    2.4 图片题

    图片题是单选题的变种,只是标题是图片而已,因此我们只需要复制一个单选题,修改标题。

    <el-image style="width: 381px; height: 411px" :src="question.title" lazy />
    

    哈哈,搞定了。

    3. 构建父组件

    题型组件已经构建完成了,那我们需要一个父组件封装适合各种题型的组件。
    这个组合采用题型进行判断:

    <template>
      <div>
        <template v-if="question.type=='single'">
          <single-item
            v-model="answerVal"
            :question="question"
            @input="handleInput"
          />
        </template>
        <template v-else-if="question.type=='multi'">
          <multi-item
            v-model="answerVal"
            :question="question"
            @input="handleInput"
          />
        </template>
        <template v-else-if="question.type=='image'">
          <image-single-item
            v-model="answerVal"
            :question="question"
            @input="handleInput"
          />
        </template>
        <template v-else-if="question.type=='text'">
          <text-item
            v-model="answerVal"
            :question="question"
            @input="handleInput"
          />
        </template>
        <template v-else>
          dont setup question type
        </template>
      </div>
    </template>
    

    实现代码如下:

    export default {
      name: 'WQuestionItem',
      components: { SingleItem, MultiItem, TextItem, ImageSingleItem },
      model: {
        prop: 'answer',
        event: 'input'
      },
      props: {    
        question: {
          type: Object,
          required: true,
          default: () => {
            return {}
          }
        },
        answer: {
          type: Object      
        },    
      },
      data() {
        return {
          answerVal: JSON.parse(JSON.stringify(this.answer))
        }
      },
      computed: {
      },  
      methods: {
        handleInput(val) {
          console.log('wquestionItem', val)
          this.$emit('input', val)
        }
      }
    }
    

    运行下效果~~~~
    在这里插入图片描述

    4 小结

    利用vue 的组件,我们实现了单选、多选、填空、图片等题型的设计,如果你还有更多的需求,可以在此基础上方便的扩展。

    关注我,不迷路;一块学习vue,一块进步!

    展开全文
  • 十分钟搞懂HTTP和HTTPS协议

    千次阅读 2019-12-25 19:39:43
    HTTP是一个基于TCP/IP通信协议来传递数据的协议,传输的数据类型为HTML 文件,、图片文件, 查询结果等。 HTTP协议一般用于B/S架构()。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。 ...
  • 1913年,丹麦物理学家波尔,提出了氢原子能量量子化模型,电子在围绕氢原子运动时,轨道只能取某些特定的值,这些特定的值满足量子化条件: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传...
  • 一、说教材1、教材分析《插入图片及剪贴画》是江西科学技术出版社小学信息技术教材四年级上册的内容,是在学生基本掌握了WORD文本的编辑和排版的基础上进行学习的。本课主要是让学生掌握在Word中插入图片的方法。...
  • 比如每隔十分钟同步一次时间。 原因:很多集群操作对时间同步的要求性很高,最高的像HBase,要求不差不能超过秒级。 注意:这只是“同步”,并不保证时间一定是正确的。 协议:ntp协议 步骤: 1. 检查ntp是否...
  • Python pandas十分钟教程

    2020-12-17 14:34:16
    如果读取的文件没有列名,需要在程序中设置header,举例如下: pd.read_csv("Soils.csv",header=None) 如果碰巧数据集中有日期时间类型的列,那么就需要在括号内设置参数parse_dates = [column_name],以便Pandas...
  • 十分钟走进大数据世界

    千次阅读 2019-02-16 09:37:36
    大数据的概念大数据(big data),指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。...
  • 十分钟了解Transformers的基本概念

    千次阅读 2020-10-20 08:46:27
    Transformer处理图片 图像不是序列。 但是可以将图像解释为一系列区块,然后通过Transformer编码器对其进行处理。 只需将图像划分为小块,并提供这些小块的线性嵌入序列即可作为Transformer Encoder的输入。 图像...
  • 十分钟学会画甘特图

    千次阅读 2021-01-16 21:49:02
    甘特图用图示的方式表现项目的各项活动以及进展情况,它有两条轴,横轴代表时间,纵轴代表项目任务,横条代表活动的计划和实际完成情况,它可以很直观地展示出各项活动的进度。对于项目管理者来说,通过甘特图可以...
  • 《哦,十分钟》说课稿各位评委老师好,我是来应聘小学音乐教师的x号考生,我今天的说课题目是《哦,十分钟》,请问可以开始我的说课了吗?接下来,我将从说教材、说学情、说教学目标及重难点、说教法、说学法、说教学...
  • Windows 7电脑中怎么设置屏幕保护时间?虽然屏幕保护时间的设置比较简单,不过对于一些不熟悉电脑的朋友来说,设置屏幕保护时间还是有些困难。因此,本文通过图文并茂的方式,来详细介绍win7中设置屏幕保护设置的...
  • arduino 时间灯控

    2021-12-19 22:34:43
    arduino 时间灯控 总体介绍一下这个灯控是具体干啥的。最开始是老师看了我那个阿里云灯控之后,然后来活了。做一个可以控制220V灯通过时间控制的通过按键设置时间的灯控,然后就有了这个创作。 开始搭建前,就要有一...
  • 话不多说我们直接来实现一个简单的TimerServer(该服务器的提供的服务是接收客户端的指令返回服务器的系统时间)。 import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io....
  • 接下来再看下右侧面板 Get Started 欢迎页签、Statistics 统计页签、inspectors 检查页签、AutoResponse 自动响应页签、composer 构建页签、log 日志页签、Filters 过滤页签、Timeline 请求响应时间、Fiddler Script...
  • 十分钟复习Bitmap

    2020-11-05 22:33:09
    -> drawable-xxxhdpi -> drawable-nodpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi 2.1.4 drawable放置建议 图片资源应该尽量放在高密度文件夹下,这样可以节省图片的内存开支,而UI在设计图片的时候也...
  • 本书所收录的所有面试题都给出了最优解讲解和代码实现,并且提供了一些普通解法和最优解法的运行时间对比,让读者真切地感受到最优解的魅力! 本书中的题目全面且经典,更重要的是,书中收录了大量独家题目和最优解...
  • 十分钟掌握Google Guice(上)

    万次阅读 多人点赞 2019-05-01 11:30:55
    要不,也对不起封面图片。 Guice支持Java&Annotation的配置方式,Spring支持XML&Annotation的配置方式。都会用到注解,Java和XML没有本质的区别,都是一门变成语言,不要歧视XML。╮(╯_╰)╭,毕竟PHP才是最好...
  • Spring Cloud和SpringBoot版本对应关系 Spring Cloud和各子项目版本对应关系 和Spring Boot的关系 Spring Cloud入门系列汇总 序号 内容 1 Spring Cloud入门-十分钟了解Spring Cloud 2 Spring Cloud入门-Eureka服务...
  • 十分钟,让你变成AI产品经理   https://www.cnblogs.com/DicksonJYL/p/9583605.html     先说一下你阅读本文可以得到什么。你能得到AI的理论知识框架;你能学习到如何成为一个AI产品经理并且了解到AI产品...
  • 十分钟看懂docker

    千次阅读 多人点赞 2021-11-27 15:04:26
    Docker与虚拟机的对比 下面的图片比较了 Docker 和传统虚拟化方式的不同之处,可见容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统, 而虚拟机传统方式则是在硬件层面实现。 传统的虚拟机首先通过...
  • 地球实时卫星图片资源-与当前时间只相差30分钟在这里跟大家分享一个有趣的项目。这个项目提供一个实时地球照片源,通过向其服务器发送请求,能抓取到当前地球的照片。对于图片壁纸类的应用来说是一个不错的图片源...
  • 最近挺火的一套源码,本人在某资源论坛VIP板块下载的收费源码。有想法的朋友拿去吧,貌似很强大。笨源码不是我本人所写 所以没有任何教程,所以也不要来问了。谢谢
  • ![图片说明](https://img-ask.csdn.net/upload/202008/17/1597646486_17242.jpg) 1、展示当前时间段之后的时间,10分钟一个间隔 2、时间默认9-12与13-18点时间段可以选择
  • 请将下面两张图片下载保存。 4.然后就进入到我们的Unity的界面了!PS.已经过去了1min。 5.看到左边的Hierarchy了吗?就标红的那个。空白处,右键选择2D Object-Sprite。 6.下面就是要创建我们的主角,呆瓜一号了!把...
  • css、js、图片:强缓存,文件名带上hash。 强缓存和协商缓存的区别: 强缓存不发送请求到服务器,所以会存在浏览器展示的页面不是最新的的情况,但是协商缓存发送请求到服务器,服务器能知道页面展示的是否为最新...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,658
精华内容 1,863
关键字:

十分钟时间图片