Android 使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚

如有转载: http://blog.csdn.net/t12x3456 随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误: **[java]** [view plain](http://blog.csdn.net/t12x3456/article/details/40837287#)[copy](http://blog.csdn.net/t12x3456/article/details/40837287#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/513619)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/513619/fork) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - UNEXPECTED TOP-LEVEL EXCEPTION: - java.lang.IllegalArgumentException: method ID not in [<span class="number"></span>, <span class="number">0xffff</span>]: <span class="number">65536</span> - at com.android.dx.merge.DexMerger$<span class="number">6</span>.updateIndex(DexMerger.java:<span class="number">501</span>) - at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:<span class="number">282</span>) - at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:<span class="number">490</span>) - at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:<span class="number">167</span>) - at com.android.dx.merge.DexMerger.merge(DexMerger.java:<span class="number">188</span>) - at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:<span class="number">439</span>) - at com.android.dx.command.dexer.Main.runMonoDex(Main.java:<span class="number">287</span>) - at com.android.dx.command.dexer.Main.run(Main.java:<span class="number">230</span>) - at com.android.dx.command.dexer.Main.main(Main.java:<span class="number">199</span>) - at com.android.dx.command.Main.main(Main.java:<span class="number">103</span>) ...

2015年6月24日 · 4 分钟 · 天边的星星

MVP框架 – Ted Mosby的软件架构

作者:Hannes Dorfmann 原文链接 : Ted Mosby – Software Architect 文章出自 : Android开发技术前线 译者 : Mr.Simple 我给这篇关于Android库的博客起的名字灵感来源于《老爸老妈浪漫史》中的建筑设计师Ted Mosby。这个Mosby库可以帮助大家在Android上通过Model-View-Presenter模式做出一个完善稳健、可重复使用的软件,还可以借助ViewState轻松实现屏幕翻转。 Model-View-Presenter (MVP) MVP模式是一个把view从低层模型分离出来的一种现代模式。MVP由model–view–controller (MVC)软件模式衍生而来,常用于构建UI MVP中的M(model)代表的是将会显示在view(UI)中的数据。 MVP中的V(view)是显示数据(model)并且将用户指令(events)传送到presenter以便作用于那些数据的一个接口。View通常含有Presenter的引用。 MVP中的P(presenter)扮演的是“中间人”的作用(就如MVC中的controller),且presenter同时引用view和model。值得注意的是,“Model”这个词并不正确。严格意义上来说,它指的应该是检索或控制一个Model的业务逻辑层。举个例子,比如你的数据库里面包含了User,而你的View想要显示一个User列表,那么Presenter会引用数据库中的业务逻辑层(比如DAO)从而查询到一个User列表。如图1-1. 从数据库中查询或显示User列表的具体流程如图1-2: 以上工作流程图应该能够说明问题了。但是,还有以下几点值得注意的地方: Presenter不是一个OnClickListener。View主要是负责处理用户输入并调用presenter相应的方法。那么问题来了,为什么不把Presenter 直接做成一个OnClickListener,从而把“转发流程”给省略掉呢?大家想想,如果这样做的话,首先,presenter需要知道view的内部构件。举个例子,如果一个View有两个按钮,且这个view在这两个按钮上都把Presenter 注册成OnClickListener的话,那么发生点击事件时Presenter (在不知道view中按钮引用等内部构件的情况下)怎么能够区分出是哪一个按钮被点击了呢?Model,View和Presenter三者应解耦。其次,如果让Presenter 执行OnClickListener,Presenter就被绑定到了Android平台上。理论上来说presenter和业务逻辑层都是纯旧式的能够与桌面应用或其他任何java应用共享的java代码。 大家在第1步和第2步中可以看到,View 只执行Presenter 指示的操作:用户点击“load user button”(第1步)后,view并没有直接显示加载动画,而是在第2步presenter明确告诉其显示加载动画后才显示的。这一Model-View-Presenter的变体称之为MVP 被动视图。这个view可以说是要多笨有多笨。这时我们需要让presenter以一种更抽象的方式来控制view。比如,presenter在调用 view.showLoading() 时并不控制view的诸如动画等具体事项。所以presenter不应调用view.startAnimation() 等方法。 通过执行MVP被动视图,并发性以及多线程更容易处理。大家可以看到,第3步中数据库查询异步运行,并且presenter作为Listener/Observer,在数据准备显示时presenter收到通知。 Android上的MVP 目前为止一切顺利。但是大家怎么样把MVP运用到自己的Android 应用上呢?第一个问题在于,我们要把MVP模式运用到什么地方?Activity上、Fragment上、还是像RelativeLayout这类的ViewGroup上?我们来看看Android平板上的Gmail应用,如图1-3: 在我看来,上图屏幕中有四个可以使用MVP的地方。我所说的“可以使用MVP的地方”是指屏幕上显示的、在逻辑上属于一个整体的UI元素。因此这些地方也可以称为是可以运用MVP的一个单独的UI单元。如图 1-4. 看起来MVP似乎很适合运用到Activity,特别是Fragment上。通常Fragment只负责显示单一的如ListView之类的内容,就像依靠MailProvider 来获取一系列Mails的InboxPresenter 控制下的 InboxView一样。但是,MVP不仅仅限于Fragment或Activity,它还可以运用到SearchView中显示的ViewGroup中。在我的大多数app里面我都在Fragment运用MVP模式。但是大家可以自行决定把MVP运用到什么地方,前提是view是独立的,这样这样presenter才能在不与其他Presenter冲突的情况下控制View。 我们为什么要实现MVP? 我们如何在不使用MVP模式时显示Email列表到Fragment? 通常,我们需要获取并且合并本地SQL数据库和从IMAP邮件服务器获取的邮件列表,然后将邮件列表绑定到收件箱view中。那么,此时fragment的代码又会是怎么样的呢?我们需要运行两个AsyncTasks 并实现一个“等待机制”(等到两个任务将两者的加载数据合并到一个单独的mail列表)。我们还需要注意的是在加载时要显示加载动画(ProgressBar),之后用ListView替代。我们需要把所有的代码放到Fragment中吗?要是加载过程中出现错误怎么办?屏幕翻转怎么办?谁来负责撤销AsyncTasks ?这一系列的问题都可以通过MVP得到解决。让我们跟那些带有上千行大杂烩代码的activity和fragment说拜拜吧 但是,在我们深入研究如何将MVP运用到Android中之前,我们需要弄清楚的一个问题是:Activity或Fragment究竟是一个View还是一个Presenter。Activity或Fragment似乎既是View也是Presenter,因为它们都有 onCreate() 或**onDestroy()**之类的生命周期回调功能,并且它们负责从一个UI控件到另一个UI 控件的转换(比如在加载时显示ProgressBar,然后显示带有数据的ListView)等View操作。大家可能会觉得这里的Activity或Fragment就是一个Controller,我猜可能也是这么一个初衷。但是在经历了几年的Android应用开发之后,我得出这么一个结论:我们应该把Activity或Fragment看作是一个不太智能的View,而不是把它们看作一个Presenter。后文我会给出原因。 综上,我想给大家介绍一个在Android平台上开发基于MVP的应用的一个 Mosby库。 Mosby 大家可以在Github和Maven Central上找到Mosby库。Mosby分为几个子模块,大家可以根据自己的需要选取组件。我们来回顾一下最重要的一个模块。 核心模块 ( Core Module) 《老爸老妈浪漫史》中的建筑设计师Ted Mosby想建造一栋摩天大楼。而建造这样一栋宏伟的建筑必须打好坚实的地基。这对Android应用的开发来说是也是一样的道理。基本上,Core Module 分为两种类型:MosbyActivity 和MosbyFragment。这两者是所有其他activity或fragment子类的基类(相当于建筑的地基)。两者都使用我们大家所熟知的APT (Annotation Processing Tool)来减少一些样板式代码。MosbyActivity 和MosbyFragment 使用Butterknife进行view的注入,使用Icepick 将实例状态保存和存储到Bundle中,使用FragmentArgs注入Fragment参数。我们不需要再调用Butterknife.inject(this)等插入方法。这类代码已经包含在了MosbyActivity 和 MosbyFragment中。它是即时可用的。我们需要做的就是使用子类中相应的注解。核心模块与MVP没有关联,它只是写一个大型软件的基础。 ...

2015年6月24日 · 34 分钟 · 天边的星星

美团Android DEX自动拆包及动态加载简介

概述 作为一个android开发者,在开发应用时,随着业务规模发展到一定程度,不断地加入新功能、添加新的类库,代码在急剧的膨胀,相应的apk包的大小也急剧增加, 那么终有一天,你会不幸遇到这个错误: 生成的apk在android 2.3或之前的机器上无法安装,提示INSTALL_FAILED_DEXOPT 方法数量过多,编译时出错,提示: Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536 而问题产生的具体原因如下: 无法安装(Android 2.3 INSTALL_FAILED_DEXOPT)问题,是由dexopt的LinearAlloc限制引起的,在Android版本不同分别经历了4M/5M/8M/16M限制,目前主流4.2.x系统上可能都已到16M, 在Gingerbread或者以下系统LinearAllocHdr分配空间只有5M大小的, 高于Gingerbread的系统提升到了8M。Dalvik linearAlloc是一个固定大小的缓冲区。在应用的安装过程中,系统会运行一个名为dexopt的程序为该应用在当前机型中运行做准备。dexopt使用LinearAlloc来存储应用的方法信息。Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB或16MB。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃。 超过最大方法数限制的问题,是由于DEX文件格式限制,一个DEX文件中method个数采用使用原生类型short来索引文件中的方法,也就是4个字节共计最多表达65536个method,field/class的个数也均有此限制。对于DEX文件,则是将工程所需全部class文件合并且压缩到一个DEX文件期间,也就是Android打包的DEX过程中, 单个DEX文件可被引用的方法总数(自己开发的代码以及所引用的Android框架、类库的代码)被限制为65536; 插件化? MultiDex? 解决这个问题,一般有下面几种方案,一种方案是加大Proguard的力度来减小DEX的大小和方法数,但这是治标不治本的方案,随着业务代码的添加,方法数终究会到达这个限制,一种比较流行的方案是插件化方案,另外一种是采用google提供的MultiDex方案,以及google在推出MultiDex之前Android Developers博客介绍的通过自定义类加载过程, 再就是Facebook推出的为Android应用开发的Dalvik补丁, 但facebook博客里写的不是很详细;我们在插件化方案上也做了探索和尝试,发现部署插件化方案,首先需要梳理和修改各个业务线的代码,使之解耦,改动的面和量比较巨大,通过一定的探讨和分析,我们认为对我们目前来说采用MultiDex方案更靠谱一些,这样我们可以快速和简洁的对代码进行拆分,同时代码改动也在可以接受的范围内; 这样我们采用了google提供的MultiDex方式进行了开发。 插件化方案在业内有不同的实现原理,这里不再一一列举,这里只列举下Google为构建超过65K方法数的应用提供官方支持的方案:MultiDex。 首先使用Android SDK Manager升级到最新的Android SDK Build Tools和Android Support Library。然后进行以下两步操作: 1.修改Gradle配置文件,启用MultiDex并包含MultiDex支持: android { compileSdkVersion 21 buildToolsVersion "21.1.0" defaultConfig { ... minSdkVersion 14 targetSdkVersion 21 ... // Enabling MultiDex support. MultiDexEnabled true } ... } dependencies { compile 'com.android.support:MultiDex:1.0.0' } 2.让应用支持多DEX文件。在官方文档中描述了三种可选方法: ...

2015年6月24日 · 4 分钟 · 天边的星星

java自带线程池和队列详细讲解,android中适用

- [一简介](http://my.oschina.net/u/1424386/blog/336087#OSC_h1_1) - [二:线程池](http://my.oschina.net/u/1424386/blog/336087#OSC_h1_2) - [三:ThreadPoolExecutor详解](http://my.oschina.net/u/1424386/blog/336087#OSC_h1_3) Java线程池使用说明 &nbsp; 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的。在jdk1.5之后这一情况有了很大的改观。Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用。为我们在开发中处理线程的问题提供了非常大的帮助。 &nbsp; 二:线程池 **线程池的作用:** 线程池作用就是限制系统中执行线程的数量。 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。 **为什么要用线程池:** 1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。 Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。 比较重要的几个类: ExecutorService <td> 真正的线程池接口。 </td> </tr> <tr> <td> ScheduledExecutorService </td> <td> 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。 </td> </tr> <tr> <td> ThreadPoolExecutor </td> <td> ExecutorService的默认实现。 </td> </tr> <tr> <td> ScheduledThreadPoolExecutor </td> <td> 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。 </td> </tr> 要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。 **1. newSingleThreadExecutor** 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 2.**newFixedThreadPool** 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 **3. newCachedThreadPool** 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程, 那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 4.**newScheduledThreadPool** 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。 **实例** **1:newSingleThreadExecutor** MyThread.java **public****class**MyThread **extends** Thread {@Override **public****void** run() { System.*out*.println(Thread.*currentThread*().getName() + &#8220;正在执行。。。&#8221;); } }</td> </tr> </tbody> </table> TestSingleThreadExecutor.java <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> **public****class**TestSingleThreadExecutor {**public****static****void** main(String[] args) { //创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.* newSingleThreadExecutor*(); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 Thread t1 = **new** MyThread(); Thread t2 = **new** MyThread(); Thread t3 = **new** MyThread(); Thread t4 = **new** MyThread(); Thread t5 = **new** MyThread(); //将线程放入池中进行执行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); //关闭线程池 pool.shutdown(); } }</td> </tr> </tbody> </table> **输出结果** <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> pool-1-thread-1正在执行。。。pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。</td> </tr> </tbody> </table> **2newFixedThreadPool** TestFixedThreadPool.Java <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> **public****class** TestFixedThreadPool {**public****static****void** main(String[] args) { //创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.*newFixedThreadPool*(2); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 Thread t1 = **new** MyThread(); Thread t2 = **new** MyThread(); Thread t3 = **new** MyThread(); Thread t4 = **new** MyThread(); Thread t5 = **new** MyThread(); //将线程放入池中进行执行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); //关闭线程池 pool.shutdown(); } }</td> </tr> </tbody> </table> **输出结果** <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> pool-1-thread-1正在执行。。。pool-1-thread-2正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-2正在执行。。。 pool-1-thread-1正在执行。。。</td> </tr> </tbody> </table> 3** newCachedThreadPool** TestCachedThreadPool.java <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> **public****class** TestCachedThreadPool {**public****static****void** main(String[] args) { //创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.*newCachedThreadPool*(); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 Thread t1 = **new** MyThread(); Thread t2 = **new** MyThread(); Thread t3 = **new** MyThread(); Thread t4 = **new** MyThread(); Thread t5 = **new** MyThread(); //将线程放入池中进行执行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); //关闭线程池 pool.shutdown(); } }</td> </tr> </tbody> </table> 输出结果: <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> pool-1-thread-2正在执行。。。pool-1-thread-4正在执行。。。 pool-1-thread-3正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-5正在执行。。。</td> </tr> </tbody> </table> 4**newScheduledThreadPool** TestScheduledThreadPoolExecutor.java <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> **public****class** TestScheduledThreadPoolExecutor {**public****static****void** main(String[] args) { ScheduledThreadPoolExecutor exec = **new** ScheduledThreadPoolExecutor(1); exec.scheduleAtFixedRate(**new** Runnable() {//每隔一段时间就触发异常 @Override **public****void** run() { //throw new RuntimeException(); System.*out*.println(&#8220;================&#8221;); } }, 1000, 5000, TimeUnit.*MILLISECONDS*); exec.scheduleAtFixedRate(**new** Runnable() {//每隔一段时间打印系统时间,证明两者是互不影响的 @Override **public****void** run() { System.*out*.println(System.*nanoTime*()); } }, 1000, 2000, TimeUnit.*MILLISECONDS*); } }</td> </tr> </tbody> </table> 输出结果 <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> ================8384644549516 8386643829034 8388643830710 ================ 8390643851383 8392643879319 8400643939383</td> </tr> </tbody> </table> &nbsp; # <a rel="nofollow" name="t2"></a>三:ThreadPoolExecutor详解 ThreadPoolExecutor的完整构造方法的签名是:**ThreadPoolExecutor**`(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue&lt;Runnable&gt; workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)` . **maximumPoolSize**-池中允许的最大线程数。 **ThreadPoolExecutor是Executors类的底层实现。** 在JDK帮助文档中,有如此一段话: “强烈建议程序员使用较为方便的`Executors`工厂方法`Executors.newCachedThreadPool()`(无界线程池,可以进行自动线程回收)、`Executors.newFixedThreadPool(int)`(固定大小线程池)`Executors.newSingleThreadExecutor()`(单个后台线程) 它们均为大多数使用场景预定义了设置。” 下面介绍一下几个类的源码: **ExecutorService newFixedThreadPool (int nThreads):固定大小线程池。** 可以看到,corePoolSize和maximumPoolSize的大小是一样的(实际上,后面会介绍,如果使用无界queue的话maximumPoolSize参数是没有意义的),keepAliveTime和unit的设值表名什么?-就是该实现不想keep alive!最后的BlockingQueue选择了LinkedBlockingQueue,该queue有一个特点,他是无界的。 <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> 1. public static ExecutorService newFixedThreadPool(int nThreads) {2. return new ThreadPoolExecutor(nThreads, nThreads, 3. 0L, TimeUnit.MILLISECONDS, 4. new LinkedBlockingQueue<Runnable>()); 5. }</td> </tr> </tbody> </table> **ExecutorService newSingleThreadExecutor():单线程** <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> 1. public static ExecutorService newSingleThreadExecutor() {2. return new FinalizableDelegatedExecutorService 3. (new ThreadPoolExecutor(1, 1, 4. 0L, TimeUnit.MILLISECONDS, 5. new LinkedBlockingQueue<Runnable>())); 6. }</td> </tr> </tbody> </table> **ExecutorService newCachedThreadPool():无界线程池,可以进行自动线程回收** 这个实现就有意思了。首先是无界的线程池,所以我们可以发现maximumPoolSize为big big。其次BlockingQueue的选择上使用SynchronousQueue。可能对于该BlockingQueue有些陌生,简单说:该QUEUE中,每个插入操作必须等待另一个线程的对应移除操作。 <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> 1. public static ExecutorService newCachedThreadPool() {2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3. 60L, TimeUnit.SECONDS, 4. new SynchronousQueue<Runnable>()); - } </td> </tr> </table> 先从[BlockingQueue](http://dongxuan.iteye.com/admin/blogs/901659/)<[Runnable](http://dongxuan.iteye.com/admin/blogs/901659/)> workQueue这个入参开始说起。在JDK中,其实已经说得很清楚了,一共有三种类型的queue。 所有BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互: 如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行) 如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,**而不添加新的线程**。 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。 **queue上的三种类型。** &nbsp; 排队有三种通用策略: **直接提交。**工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 **无界队列。**使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 **有界队列。**当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。 **BlockingQueue的选择。** **例子一:使用直接提交策略,也即SynchronousQueue。** 首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,**在某次添加元素后必须等待其他线程取走后才能继续添加**。在这里不是核心线程便是新创建的线程,但是我们试想一样下,下面的场景。 我们使用一下参数构造ThreadPoolExecutor: 1. new ThreadPoolExecutor( 2. 2, 3, 30, TimeUnit.SECONDS, 3. new SynchronousQueue<Runnable>(), 4. new RecorderThreadFactory(&#8220;CookieRecorderPool&#8221;), - new ThreadPoolExecutor.CallerRunsPolicy()); new ThreadPoolExecutor( 2, 3, 30, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new RecorderThreadFactory(&#8220;CookieRecorderPool&#8221;), new ThreadPoolExecutor.CallerRunsPolicy()); 当核心线程已经有2个正在运行. - 此时继续来了一个任务(A),根据前面介绍的“如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,**而不添加新的线程**。”,所以A被添加到queue中。 - 又来了一个任务(B),且核心2个线程还没有忙完,OK,接下来首先尝试1中描述,但是由于使用的SynchronousQueue,所以一定无法加入进去。 - 此时便满足了上面提到的“如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。”,所以必然会新建一个线程来运行这个任务。 - 暂时还可以,但是如果这三个任务都还没完成,连续来了两个任务,第一个添加入queue中,后一个呢?queue中无法插入,而线程数达到了maximumPoolSize,所以只好执行异常策略了。 所以在使用SynchronousQueue通常要求maximumPoolSize是无界的,这样就可以避免上述情况发生(如果希望限制就直接使用有界队列)。对于使用SynchronousQueue的作用jdk中写的很清楚:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。 什么意思?如果你的任务A1,A2有内部关联,A1需要先运行,那么先提交A1,再提交A2,当使用SynchronousQueue我们可以保证,A1必定先被执行,在A1么有被执行前,A2不可能添加入queue中。 **例子二:使用无界队列策略,即LinkedBlockingQueue** 这个就拿**newFixedThreadPool**来说,根据前文提到的规则: 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。那么当任务继续增加,会发生什么呢? 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。OK,此时任务变加入队列之中了,那什么时候才会添加新线程呢? 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。这里就很有意思了,可能会出现无法加入队列吗?不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,不一会儿就爆了。 **例子三:有界队列,使用ArrayBlockingQueue。** 这个是最为复杂的使用,所以JDK不推荐使用也有些道理。与上面的相比,最大的特点便是可以防止资源耗尽的情况发生。 举例来说,请看如下构造方法: 1. new ThreadPoolExecutor( 2. 2, 4, 30, TimeUnit.SECONDS, 3. new ArrayBlockingQueue<Runnable>(2), 4. new RecorderThreadFactory(&#8220;CookieRecorderPool&#8221;), 5. new ThreadPoolExecutor.CallerRunsPolicy()); new ThreadPoolExecutor( 2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), new RecorderThreadFactory(&#8220;CookieRecorderPool&#8221;), new ThreadPoolExecutor.CallerRunsPolicy()); 假设,所有的任务都永远无法执行完。 对于首先来的A,B来说直接运行,接下来,如果来了C,D,他们会被放到queue中,如果接下来再来E,F,则增加线程运行E,F。但是如果再来任务,队列无法再接受了,线程数也到达最大的限制了,所以就会使用拒绝策略来处理。 **keepAliveTime** jdk中的解释是:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 有点拗口,其实这个不难理解,在使用了“池”的应用中,大多都有类似的参数需要配置。比如数据库连接池,DBCP中的maxIdle,minIdle参数。 什么意思?接着上面的解释,后来向老板派来的工人始终是“借来的”,俗话说“有借就有还”,但这里的问题就是什么时候还了,如果借来的工人刚完成一个任务就还回去,后来发现任务还有,那岂不是又要去借?这一来一往,老板肯定头也大死了。 &nbsp; 合理的策略:既然借了,那就多借一会儿。直到“某一段”时间后,发现再也用不到这些工人时,便可以还回去了。这里的某一段时间便是keepAliveTime的含义,TimeUnit为keepAliveTime值的度量。 &nbsp; **RejectedExecutionHandler** 另一种情况便是,即使向老板借了工人,但是任务还是继续过来,还是忙不过来,这时整个队伍只好拒绝接受了。 RejectedExecutionHandler接口提供了对于拒绝任务的处理的自定方法的机会。在ThreadPoolExecutor中已经默认包含了4中策略,因为源码非常简单,这里直接贴出来。 **CallerRunsPolicy**:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。 1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 2. if (!e.isShutdown()) { 3. r.run(); 4. } 5. } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } 这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。 **AbortPolicy:**处理程序遭到拒绝将抛出运行时RejectedExecutionException 1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 2. throw new RejectedExecutionException(); 3. } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException(); } 这种策略直接抛出异常,丢弃任务。 **DiscardPolicy:**不能执行的任务将被删除 1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 2. } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } 这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。 **DiscardOldestPolicy:**如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程) 1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 2. if (!e.isShutdown()) { 3. e.getQueue().poll(); 4. e.execute(r); 5. } - } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } 该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略需要适当小心。 设想:如果其他线程都还在运行,那么新来任务踢掉旧任务,缓存在queue中,再来一个任务又会踢掉queue中最老任务。 总结: keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。 反之,如果核心数较小,有界BlockingQueue数值又较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。 <table border="1" cellspacing="0" cellpadding="0"> <tr> <td> </td> </tr> </table> public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }

2015年6月15日 · 4 分钟 · 天边的星星

让Android Support V4中的SwipeRefreshLayout支持上拉加载更多

前言 原 来的Android SDK中并没有下拉刷新组件,但是这个组件确实绝大多数APP必备的一个部件。好在google在v4包中出了一个 SwipeRefreshLayout,但是这个组件只支持下拉刷新,不支持上拉加载更多的操作。因此,我们就来简单的扩展一下这个组件以实现上拉下载的 目的。 基本原理 上 拉加载或者说滚动到底部时自动加载,都是通过判断是否滚动到了ListView或者其他View的底部,然后触发相应的操作,这里我们以ListView 来说明。因此我们需要在监听ListView的滚动事件,当ListView滚动到底部时自动触发加载操作;但是当用户支持手指滑动屏幕,没有滚动时,我 们也需要让它加载,因此这种情形就是上拉加载更多。所以我们需要在触摸事件里面进行判断,如果到了底部,且用户是上拉操作,那么执行加载更多。更多关于下 拉刷新、上拉加载请参考[打造通用的Android下拉刷新组件(适用于ListView、GridView等各类View)](http://blog.csdn.net/bboyfeiyu/article/details/39718861)。 时间有限,直接上代码吧。 实现代码 **[java]** [view plain](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[copy](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/503362)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/503362/fork) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> </div> - <span class="comment">/**</span> - <span class="comment"> * 继承自SwipeRefreshLayout,从而实现滑动到底部时上拉加载更多的功能.</span> - <span class="comment"> * </span> - <span class="comment"> * @author mrsimple</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">class</span> RefreshLayout <span class="keyword">extends</span> SwipeRefreshLayout <span class="keyword">implements</span> OnScrollListener { - - <span class="comment">/**</span> - <span class="comment"> * 滑动到最下面时的上拉操作</span> - <span class="comment"> */</span> - - <span class="keyword">private</span> <span class="keyword">int</span> mTouchSlop; - <span class="comment">/**</span> - <span class="comment"> * listview实例</span> - <span class="comment"> */</span> - <span class="keyword">private</span> ListView mListView; - - <span class="comment">/**</span> - <span class="comment"> * 上拉监听器, 到了最底部的上拉加载操作</span> - <span class="comment"> */</span> - <span class="keyword">private</span> OnLoadListener mOnLoadListener; - - <span class="comment">/**</span> - <span class="comment"> * ListView的加载中footer</span> - <span class="comment"> */</span> - <span class="keyword">private</span> View mListViewFooter; - - <span class="comment">/**</span> - <span class="comment"> * 按下时的y坐标</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mYDown; - <span class="comment">/**</span> - <span class="comment"> * 抬起时的y坐标, 与mYDown一起用于滑动到底部时判断是上拉还是下拉</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mLastY; - <span class="comment">/**</span> - <span class="comment"> * 是否在加载中 ( 上拉加载更多 )</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> isLoading = <span class="keyword">false</span>; - - <span class="comment">/**</span> - <span class="comment"> * @param context</span> - <span class="comment"> */</span> - <span class="keyword">public</span> RefreshLayout(Context context) { - <span class="keyword">this</span>(context, <span class="keyword">null</span>); - } - - <span class="keyword">public</span> RefreshLayout(Context context, AttributeSet attrs) { - <span class="keyword">super</span>(context, attrs); - - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - - mListViewFooter = LayoutInflater.from(context).inflate(R.layout.listview_footer, <span class="keyword">null</span>, - <span class="keyword">false</span>); - } - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onLayout(<span class="keyword">boolean</span> changed, <span class="keyword">int</span> left, <span class="keyword">int</span> top, <span class="keyword">int</span> right, <span class="keyword">int</span> bottom) { - <span class="keyword">super</span>.onLayout(changed, left, top, right, bottom); - - <span class="comment">// 初始化ListView对象</span> - <span class="keyword">if</span> (mListView == <span class="keyword">null</span>) { - getListView(); - } - } - - <span class="comment">/**</span> - <span class="comment"> * 获取ListView对象</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">void</span> getListView() { - <span class="keyword">int</span> childs = getChildCount(); - <span class="keyword">if</span> (childs > <span class="number"></span>) { - View childView = getChildAt(<span class="number"></span>); - <span class="keyword">if</span> (childView <span class="keyword">instanceof</span> ListView) { - mListView = (ListView) childView; - <span class="comment">// 设置滚动监听器给ListView, 使得滚动的情况下也可以自动加载</span> - mListView.setOnScrollListener(<span class="keyword">this</span>); - Log.d(VIEW_LOG_TAG, <span class="string">&#8220;### 找到listview&#8221;</span>); - } - } - } - - <span class="comment">/*</span> - <span class="comment"> * (non-Javadoc)</span> - <span class="comment"> * @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent)</span> - <span class="comment"> */</span> - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">boolean</span> dispatchTouchEvent(MotionEvent event) { - <span class="keyword">final</span> <span class="keyword">int</span> action = event.getAction(); - - <span class="keyword">switch</span> (action) { - <span class="keyword">case</span> MotionEvent.ACTION_DOWN: - <span class="comment">// 按下</span> - mYDown = (<span class="keyword">int</span>) event.getRawY(); - <span class="keyword">break</span>; - - <span class="keyword">case</span> MotionEvent.ACTION_MOVE: - <span class="comment">// 移动</span> - mLastY = (<span class="keyword">int</span>) event.getRawY(); - <span class="keyword">break</span>; - - <span class="keyword">case</span> MotionEvent.ACTION_UP: - <span class="comment">// 抬起</span> - <span class="keyword">if</span> (canLoad()) { - loadData(); - } - <span class="keyword">break</span>; - <span class="keyword">default</span>: - <span class="keyword">break</span>; - } - - <span class="keyword">return</span> <span class="keyword">super</span>.dispatchTouchEvent(event); - } - - <span class="comment">/**</span> - <span class="comment"> * 是否可以加载更多, 条件是到了最底部, listview不在加载中, 且为上拉操作.</span> - <span class="comment"> * </span> - <span class="comment"> * @return</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> canLoad() { - <span class="keyword">return</span> isBottom() && !isLoading && isPullUp(); - } - - <span class="comment">/**</span> - <span class="comment"> * 判断是否到了最底部</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> isBottom() { - - <span class="keyword">if</span> (mListView != <span class="keyword">null</span> && mListView.getAdapter() != <span class="keyword">null</span>) { - <span class="keyword">return</span> mListView.getLastVisiblePosition() == (mListView.getAdapter().getCount() &#8211; <span class="number">1</span>); - } - <span class="keyword">return</span> <span class="keyword">false</span>; - } - - <span class="comment">/**</span> - <span class="comment"> * 是否是上拉操作</span> - <span class="comment"> * </span> - <span class="comment"> * @return</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> isPullUp() { - <span class="keyword">return</span> (mYDown &#8211; mLastY) >= mTouchSlop; - } - - <span class="comment">/**</span> - <span class="comment"> * 如果到了最底部,而且是上拉操作.那么执行onLoad方法</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">void</span> loadData() { - <span class="keyword">if</span> (mOnLoadListener != <span class="keyword">null</span>) { - <span class="comment">// 设置状态</span> - setLoading(<span class="keyword">true</span>); - <span class="comment">//</span> - mOnLoadListener.onLoad(); - } - } - - <span class="comment">/**</span> - <span class="comment"> * @param loading</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">void</span> setLoading(<span class="keyword">boolean</span> loading) { - isLoading = loading; - <span class="keyword">if</span> (isLoading) { - mListView.addFooterView(mListViewFooter); - } <span class="keyword">else</span> { - mListView.removeFooterView(mListViewFooter); - mYDown = <span class="number"></span>; - mLastY = <span class="number"></span>; - } - } - - <span class="comment">/**</span> - <span class="comment"> * @param loadListener</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">void</span> setOnLoadListener(OnLoadListener loadListener) { - mOnLoadListener = loadListener; - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onScrollStateChanged(AbsListView view, <span class="keyword">int</span> scrollState) { - - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onScroll(AbsListView view, <span class="keyword">int</span> firstVisibleItem, <span class="keyword">int</span> visibleItemCount, - <span class="keyword">int</span> totalItemCount) { - <span class="comment">// 滚动时到了最底部也可以加载更多</span> - <span class="keyword">if</span> (canLoad()) { - loadData(); - } - } - - <span class="comment">/**</span> - <span class="comment"> * 加载更多的监听器</span> - <span class="comment"> * </span> - <span class="comment"> * @author mrsimple</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">interface</span> OnLoadListener { - <span class="keyword">public</span> <span class="keyword">void</span> onLoad(); - } - } listview_footer.xml: **[html]** [view plain](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[copy](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/503362)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/503362/fork) <div> <embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_2"> </embed> </div> </div> </div> - <span class="tag"><?</span><span class="tag-name">xml</span> <span class="attribute">version</span>=<span class="attribute-value">&#8220;1.0&#8221;</span> <span class="attribute">encoding</span>=<span class="attribute-value">&#8220;utf-8&#8221;</span><span class="tag">?></span> - <span class="tag"><</span><span class="tag-name">RelativeLayout</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">&#8220;http://schemas.android.com/apk/res/android&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;fill_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:background</span>=<span class="attribute-value">&#8220;@color/umeng_comm_comments_bg&#8221;</span> - <span class="attribute">android:gravity</span>=<span class="attribute-value">&#8220;center&#8221;</span> - <span class="attribute">android:paddingBottom</span>=<span class="attribute-value">&#8220;8dip&#8221;</span> - <span class="attribute">android:paddingTop</span>=<span class="attribute-value">&#8220;5dip&#8221;</span> <span class="tag">></span> - - <span class="tag"><</span><span class="tag-name">ProgressBar</span> - <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/pull_to_refresh_load_progress&#8221;</span> - <span class="attribute">style</span>=<span class="attribute-value">&#8220;@android:style/Widget.ProgressBar.Small.Inverse&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:layout_centerVertical</span>=<span class="attribute-value">&#8220;true&#8221;</span> - <span class="attribute">android:layout_centerHorizontal</span>=<span class="attribute-value">&#8220;true&#8221;</span> - <span class="attribute">android:paddingRight</span>=<span class="attribute-value">&#8220;100dp&#8221;</span> - <span class="attribute">android:indeterminate</span>=<span class="attribute-value">&#8220;true&#8221;</span> <span class="tag">/></span> - - <span class="tag"><</span><span class="tag-name">TextView</span> - <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/pull_to_refresh_loadmore_text&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;fill_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:layout_gravity</span>=<span class="attribute-value">&#8220;center&#8221;</span> - <span class="attribute">android:gravity</span>=<span class="attribute-value">&#8220;center&#8221;</span> - <span class="attribute">android:paddingTop</span>=<span class="attribute-value">&#8220;5dip&#8221;</span> - <span class="attribute">android:text</span>=<span class="attribute-value">&#8220;@string/load&#8221;</span> - <span class="attribute">android:textAppearance</span>=<span class="attribute-value">&#8220;?android:attr/textAppearanceMedium&#8221;</span> - <span class="attribute">android:textColor</span>=<span class="attribute-value">&#8220;@android:color/darker_gray&#8221;</span> - <span class="attribute">android:textSize</span>=<span class="attribute-value">&#8220;14sp&#8221;</span> - <span class="attribute">android:textStyle</span>=<span class="attribute-value">&#8220;bold&#8221;</span> <span class="tag">/></span> - - <span class="tag"></</span><span class="tag-name">RelativeLayout</span><span class="tag">></span> 使用示例 refresh.xml布局文件: **[html]** [view plain](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[copy](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/503362)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/503362/fork) <div> <embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_3"> </embed> </div> </div> </div> - <span class="tag"><?</span><span class="tag-name">xml</span> <span class="attribute">version</span>=<span class="attribute-value">&#8220;1.0&#8221;</span> <span class="attribute">encoding</span>=<span class="attribute-value">&#8220;utf-8&#8221;</span><span class="tag">?></span> - <span class="tag"><</span><span class="tag-name">myview.RefreshLayout</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">&#8220;http://schemas.android.com/apk/res/android&#8221;</span> - <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/swipe_layout&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> <span class="tag">></span> - - <span class="tag"><</span><span class="tag-name">ListView</span> - <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/listview&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> <span class="tag">></span> - <span class="tag"></</span><span class="tag-name">ListView</span><span class="tag">></span> - - <span class="tag"></</span><span class="tag-name">myview.RefreshLayout</span><span class="tag">></span> activity中的使用 : **[java]** [view plain](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[copy](http://blog.csdn.net/bboyfeiyu/article/details/39935329#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/503362)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/503362/fork) <div> <embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_4"> </embed> </div> </div> </div> - <span class="comment">/**</span> - <span class="comment"> * @author mrsimple</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">class</span> MainActivity <span class="keyword">extends</span> Activity { - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onCreate(Bundle savedInstanceState) { - <span class="keyword">super</span>.onCreate(savedInstanceState); - - setContentView(R.layout.refresh); - - <span class="comment">// 模拟一些数据</span> - <span class="keyword">final</span> List<String> datas = <span class="keyword">new</span> ArrayList<String>(); - <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < <span class="number">20</span>; i++) { - datas.add(<span class="string">&#8220;item &#8211; &#8220;</span> + i); - } - - <span class="comment">// 构造适配器</span> - <span class="keyword">final</span> BaseAdapter adapter = <span class="keyword">new</span> ArrayAdapter<String>(<span class="keyword">this</span>, - android.R.layout.simple_list_item_1, - datas); - <span class="comment">// 获取listview实例</span> - ListView listView = (ListView) findViewById(R.id.listview); - listView.setAdapter(adapter); - - <span class="comment">// 获取RefreshLayout实例</span> - <span class="keyword">final</span> RefreshLayout myRefreshListView = (RefreshLayout) - findViewById(R.id.swipe_layout); - - <span class="comment">// 设置下拉刷新时的颜色值,颜色值需要定义在xml中</span> - myRefreshListView - .setColorScheme(R.color.umeng_comm_text_topic_light_color, - R.color.umeng_comm_yellow, R.color.umeng_comm_green, - R.color.umeng_comm_linked_text); - <span class="comment">// 设置下拉刷新监听器</span> - myRefreshListView.setOnRefreshListener(<span class="keyword">new</span> OnRefreshListener() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onRefresh() { - - Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">&#8220;refresh&#8221;</span>, Toast.LENGTH_SHORT).show(); - - myRefreshListView.postDelayed(<span class="keyword">new</span> Runnable() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> run() { - <span class="comment">// 更新数据</span> - datas.add(<span class="keyword">new</span> Date().toGMTString()); - adapter.notifyDataSetChanged(); - <span class="comment">// 更新完后调用该方法结束刷新</span> - myRefreshListView.setRefreshing(<span class="keyword">false</span>); - } - }, <span class="number">1000</span>); - } - }); - - <span class="comment">// 加载监听器</span> - myRefreshListView.setOnLoadListener(<span class="keyword">new</span> OnLoadListener() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onLoad() { - - Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">&#8220;load&#8221;</span>, Toast.LENGTH_SHORT).show(); - - myRefreshListView.postDelayed(<span class="keyword">new</span> Runnable() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> run() { - datas.add(<span class="keyword">new</span> Date().toGMTString()); - adapter.notifyDataSetChanged(); - <span class="comment">// 加载完后调用该方法</span> - myRefreshListView.setLoading(<span class="keyword">false</span>); - } - }, <span class="number">1500</span>); - - } - }); - } - - } 效果图 ![](http://img.blog.csdn.net/20141009181845718) github链接 : [下拉刷新库](https://github.com/bboyfeiyu/android_my_pull_refresh_view) 。 示例在android_my_pull_demo工程中,进入demo后点击最后一个按钮查看效果即可。注意,在刷新和加载时,需要有一定的延时才能看到效果,这里我们用postDelay来模拟网络请求等延时操作,否则将看不到加载效果。

2015年6月12日 · 7 分钟 · 天边的星星

Android下拉刷新上拉加载控件,对所有View通用!

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38868463 前面写过一篇关于下拉刷新控件的博客下拉刷新控件终结者:PullToRefreshLayout,后来看到好多人还有上拉加载更多的需求,于是就在前面下拉刷新控件的基础上进行了改进,加了上拉加载的功能。不仅如此,我已经把它改成了对所有View都通用!可以随心所欲使用这两个功能~~ 我做了一个大集合的demo,实现了ListView、GridView、ExpandableListView、ScrollView、WebView、ImageView、TextView的下拉刷新和上拉加载。后面会提供demo的下载地址。(csdn上的demo有小bug,最新代码已上传到github:https://github.com/jingchenUSTC/PullToRefreshAndLoad) 依照惯例,下面将会是一大波效果图: demo首页也是可下拉的ListView,在底下可以加入table: ListView: GridView: ExpandableListView: ScrollView: WebView: ImageView: TextView: 很不错吧?最后的ImageView和TextView是最简单的,直接在下面的接口方法里返回true。 增加上拉加载很简单,和管理下拉头一样,再多管理一个上拉头,也不费事;至于把它改成通用的就需要统一一下View的行为了,为此,我定义了这样一个接口: **[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38868463#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38868463#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">package</span> com.jingchen.pulltorefresh.pullableview; - - <span class="keyword">public</span> <span class="keyword">interface</span> Pullable - { - <span class="comment">/**</span> - <span class="comment"> * 判断是否可以下拉,如果不需要下拉功能可以直接return false</span> - <span class="comment"> * </span> - <span class="comment"> * @return true如果可以下拉否则返回false</span> - <span class="comment"> */</span> - <span class="keyword">boolean</span> canPullDown(); - - <span class="comment">/**</span> - <span class="comment"> * 判断是否可以上拉,如果不需要上拉功能可以直接return false</span> - <span class="comment"> * </span> - <span class="comment"> * @return true如果可以上拉否则返回false</span> - <span class="comment"> */</span> - <span class="keyword">boolean</span> canPullUp(); - } 从 接口名就可以看出它是一个提供判断是否可拉的方法的接口。这个接口的两个方法,canPullDown()是判断何时可以下拉的方 法,canPullUp()则是判断何时可以上拉,我在demo中的判断是滑到顶部的时候可以下拉,滑到底部的时候可以上拉。所有需要上拉和下拉的 View都需要实现这个接口。后面会给出一些View的实现。先来看看改进后的自定义的布局PullToRefreshLayout,增加了一个上拉头, 下拉头和上拉头之间的View是实现了Pullable接口的pullableView。相比前面的版本,这里有改动的需要注意的地方如下: ...

2015年6月12日 · 18 分钟 · 天边的星星

Android动画机制全解析

导论 本文着重讲解Android3.0后推出的属性动画框架Property Animation——Animator。 产生原因 3.0之前已有的动画框架——Animation存在一些局限性, Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用invalidate()函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能相应事件。 而在Animator框架中使用最多的是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator进行更精细化控制,只控制一个对象的一个属性值,多个ObjectAnimator组合到AnimatorSet形成一个动画。而且ObjectAnimator能够自动驱动,可以调用setFrameDelay(longframeDelay)设置动画帧之间的间隙时间,调整帧率,减少动画过程中频繁绘制界面,而在不影响动画效果的前提下减少CPU资源消耗。因此,Anroid推出的强大的属性动画框架,基本可以实现所有的动画效果。 强大的原因 因为属性动画框架操作的是真实的属性值,直接变化了对象的属性,因此可以很灵活的实现各种效果,而不局限于以前的4种动画效果。 ObjectAnimator ObjectAnimator是属性动画框架中最重要的实行类,创建一个ObjectAnimator只需通过他的静态工厂类直接返回一个ObjectAnimator对象。传的参数包括一个对象和对象的属性名字,但这个属性必须有get和set函数,内部会通过java反射机制来调用set函数修改对象属性值。还包括属性的初始值,最终值,还可以调用setInterpolator设置曲线函数。 ObjectAnimator实例 [view plain](http://www.netfoucs.com/article/x359981514/92198.html#)[copy to clipboard](http://www.netfoucs.com/article/x359981514/92198.html#)[print](http://www.netfoucs.com/article/x359981514/92198.html#)[?](http://www.netfoucs.com/article/x359981514/92198.html#) - ObjectAnimator.ofFloat(view, <span class="string">&#8220;rotationX&#8221;</span>, <span class="number"></span>.0F, <span class="number">360</span>.0F).setDuration(<span class="number">1000</span>).start(); 这个例子很简单,针对view的属性rotationX进行持续时间为1000ms的0到360的角度变换。 PS:可操纵的属性参数:x/y;scaleX/scaleY;rotationX/ rotationY;transitionX/ transitionY等等。 PS:X是View最终的位置、translationX为最终位置与布局时初始位置的差。所以若就用translationX即为在原来基础上移动多少,X为最终多少。getX()的值为getLeft()与getTranslationX()的和。 动画绘制过程的监听 [view plain](http://www.netfoucs.com/article/x359981514/92198.html#)[copy to clipboard](http://www.netfoucs.com/article/x359981514/92198.html#)[print](http://www.netfoucs.com/article/x359981514/92198.html#)[?](http://www.netfoucs.com/article/x359981514/92198.html#) - animator.addUpdateListener(<span class="keyword">new</span> AnimatorUpdateListener() { <span class="annotation">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> onAnimationUpdate(ValueAnimator arg0) { } }); 该方法用来监听动画绘制过程中的每一帧的改变,通过这个方法,我们可以在动画重绘的过程中,实现自己的逻辑。 同时修改多个属性值 当然这个可以使用Animationset来实现,这里我们使用一种取巧的方法来实现: [view plain](http://www.netfoucs.com/article/x359981514/92198.html#)[copy to clipboard](http://www.netfoucs.com/article/x359981514/92198.html#)[print](http://www.netfoucs.com/article/x359981514/92198.html#)[?](http://www.netfoucs.com/article/x359981514/92198.html#) - ObjectAnimator anim = ObjectAnimator.ofFloat(view, <span class="string">&#8220;xxx&#8221;</span>, <span class="number">1</span>.0F, <span class="number"></span>.0F) .setDuration(<span class="number">500</span>); anim.start(); anim.addUpdateListener(<span class="keyword">new</span> AnimatorUpdateListener() { <span class="annotation">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> onAnimationUpdate(ValueAnimator animation) { floatcVal = (Float) animation.getAnimatedValue(); view.setAlpha(cVal); view.setScaleX(cVal); view.setScaleY(cVal); } }); 我们可以监听一个并不存在的属性,而在监听动画更新的方法中,去修改view的属性,监听一个不存在的属性的原因就是,我们只需要动画的变化值,通过这个值,我们自己来实现要修改的效果,实际上,更直接的方法,就是使用ValueAnimator来实现,其实ObjectAnimator就是ValueAnimator的子类,这个在下面会具体讲到。 ...

2015年6月11日 · 8 分钟 · 天边的星星

Material Design开发利器

Android 5.0 Lollipop 是迄今为止最重大的一次发布,很大程度上是因为 material design —— 这是一门新的设计语言,它刷新了整个 Android 的用户体验。但是对于开发者来说,要设计出完全符合 material design 哲学的应用,是一个很大的挑战。Android Design Support Library 对此提供了很好的支持,里面汇集了很多重要的 material design 控件,支持所有 Android 2.1 及后续版本。里面你可以看到 navigation drawer view、floating labels、floating action button、snackbar、tabs,以及一套将它们紧密结合在一起的动作与滚动框架。 Navigation View(导航视图) 无论从应用标识、内容导航,还是设计一致性来讲,navigation drawer 都是首当其冲的焦点。现在,NavigationView 让导航栏变得更简单,它提供了 navigation drawer 需要的框架,以及通过资源文件来自定义更多菜单项的能力。 ![navigationview](http://ac-lhzo7z96.clouddn.com/1433285108918) 你只需要将 NavigationView 作为 DrawerLayout 的内容视图来使用即可,例如: ![drawerlayout](http://ac-lhzo7z96.clouddn.com/1433297055793) 这里你会注意到两个属性:app:heanderLaytout 用来控制 header 部分的布局;app:menu 指定了菜单资源。NavigationView 自动处理了状态栏的变化,保证可以在 API 21+ 的设备上正确运行。 最简单的 drawer 菜单就是一个允许选择的菜单项集合,例如: ![simplemenu](http://ac-lhzo7z96.clouddn.com/1433297095098) 选中的菜单会高亮显示,以提醒用户当前选择的是哪个菜单项。 你也可以在菜单中使用 subheader 来实现独立的分组: ![subheader_menu](http://ac-lhzo7z96.clouddn.com/1433297134486) 调用 setNavigationItemSelectedListener() 后,在菜单项被选中的时候,你会通过OnNavigationItemSelectedListener 得到回调。在处理回调时,你会知道是哪个菜单项被点击,此时你可以处理选择事件,修改选中状态,加载新的内容,以及通过代码来关闭 drawer,或者其他任何你想执行的操作。 文字输入时的悬浮标签 尽管 EditText 已经为 material design 做了一些改善,但是还不够,譬如它在我们输入第一个字符的时候,就会自动隐藏掉提示标签。现在你该使用 TextInputLayout 了,它会在用户开始输入之后,自动将提示标签悬浮到 EditText 上方,这样用户就永远都能知道输入内容的上下文。 ...

2015年6月6日 · 2 分钟 · 天边的星星

Android应用层View绘制流程与源码分析

1 背景 还记得前面《Android应用setContentView与LayoutInflater加载解析机制源码分析》这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原理,记不记得最终分析结果就是下面的关系: 看见没有,如上图中id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归的实现。 前面《Android触摸屏事件派发机制详解与源码分析一(View篇)》文章的3-1小节说过Android中的任何一个布局、任何一个控件其实都是直接或间接继承自View实现的,当然也包括我们后面一步一步引出的自定义控件也不例外,所以说这些View应该都具有相同的绘制流程与机制才能显示到屏幕上(因为他们都具备相同的父类View,可能每个控件的具体绘制逻辑有差异,但是主流程都是一样的)。经过总结发现每一个View的绘制过程都必须经历三个最主要的过程,也就是measure、layout和draw。 既然一个View的绘制主要流程是这三步,那一定有一个开始地方呀,就像一个类从main函数执行一样呀。对于View的绘制开始调运地方这里先给出结论,本文后面会反过来分析原因的,先往下看就行。具体结论如下: 整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法(这个方法巨长)开始的,该函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个,如下: `&lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">performTraversals&lt;/span>() { ...... &lt;span class="hljs-comment">//最外层的根视图的widthMeasureSpec和heightMeasureSpec由来&lt;/span> &lt;span class="hljs-comment">//lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); &lt;span class="hljs-keyword">int&lt;/span> childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ...... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... mView.layout(&lt;span class="hljs-number">0&lt;/span>, &lt;span class="hljs-number">0&lt;/span>, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ...... mView.draw(canvas); ...... }` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ` &lt;span class="hljs-javadoc">/** * Figures out the measure spec for the root view in a window based on it's * layout params. * *&lt;span class="hljs-javadoctag"> @param&lt;/span> windowSize * The available width or height of the window * *&lt;span class="hljs-javadoctag"> @param&lt;/span> rootDimension * The layout params for one dimension (width or height) of the * window. * *&lt;span class="hljs-javadoctag"> @return&lt;/span> The measure spec to use to measure the root view. */&lt;/span> &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> &lt;span class="hljs-title">getRootMeasureSpec&lt;/span>(&lt;span class="hljs-keyword">int&lt;/span> windowSize, &lt;span class="hljs-keyword">int&lt;/span> rootDimension) { &lt;span class="hljs-keyword">int&lt;/span> measureSpec; &lt;span class="hljs-keyword">switch&lt;/span> (rootDimension) { &lt;span class="hljs-keyword">case&lt;/span> ViewGroup.LayoutParams.MATCH_PARENT: &lt;span class="hljs-comment">// Window can't resize. Force root view to be windowSize.&lt;/span> measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); &lt;span class="hljs-keyword">break&lt;/span>; ...... } &lt;span class="hljs-keyword">return&lt;/span> measureSpec; }` 1 ...

2015年6月6日 · 19 分钟 · 天边的星星

Android自定义控件(状态提示图表)

1 背景 前面分析那么多系统源码了,也该暂停下来休息一下,趁昨晚闲着看见一个有意思的需求就操练一下分析源码后的实例演练—-自定义控件。 这个实例很适合新手入门自定义控件。先看下效果图: 横屏模式如下: 竖屏模式如下: 看见没有,这个控件完全自定义的,连文字等都是自定义的,没有任何图片等资源,就仅仅是一个小的java文件,这个界面只有一个控件。如下咱们看下实现代码。 !!!!!!! 下载Demo工程源码点击我 【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】 2 实例代码 如下就是整个工程的源码了。 自定义上面展示的控件AreaChartsView源码: `&lt;span class="hljs-javadoc">/** * Author : yanbo * Date : 2015-06-03 * Time : 09:22 * Description : 自定义区域描述图表View */&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-class">&lt;span class="hljs-keyword">class&lt;/span> &lt;span class="hljs-title">AreaChartsView&lt;/span> &lt;span class="hljs-keyword">extends&lt;/span> &lt;span class="hljs-title">View&lt;/span> {&lt;/span> &lt;span class="hljs-keyword">private&lt;/span> Paint mPaint; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[] mZeroPos = &lt;span class="hljs-keyword">new&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[&lt;span class="hljs-number">2&lt;/span>]; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[] mMaxYPos = &lt;span class="hljs-keyword">new&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[&lt;span class="hljs-number">2&lt;/span>]; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[] mMaxXPos = &lt;span class="hljs-keyword">new&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[&lt;span class="hljs-number">2&lt;/span>]; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mWidth, mHight; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mRealWidth, mRealHight; &lt;span class="hljs-keyword">private&lt;/span> String mTitleY, mTitleX; &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mXLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mYLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;String&gt; mGridLevelText = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mGridColorLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mGridTxtColorLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mGridLevel = mXLevel.size() - &lt;span class="hljs-number">1&lt;/span>; &lt;span class="hljs-comment">//title字符大小&lt;/span> &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mXYTitleTextSize = &lt;span class="hljs-number">40&lt;/span>; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mMeasureXpos, mMeasureYpos; &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-title">AreaChartsView&lt;/span>(Context context, AttributeSet attrs) { &lt;span class="hljs-keyword">super&lt;/span>(context, attrs); mPaint = &lt;span class="hljs-keyword">new&lt;/span> Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setAntiAlias(&lt;span class="hljs-keyword">true&lt;/span>); mPaint.setFilterBitmap(&lt;span class="hljs-keyword">true&lt;/span>); } &lt;span class="hljs-annotation">@Override&lt;/span> &lt;span class="hljs-keyword">protected&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">onLayout&lt;/span>(&lt;span class="hljs-keyword">boolean&lt;/span> changed, &lt;span class="hljs-keyword">int&lt;/span> left, &lt;span class="hljs-keyword">int&lt;/span> top, &lt;span class="hljs-keyword">int&lt;/span> right, &lt;span class="hljs-keyword">int&lt;/span> bottom) { &lt;span class="hljs-keyword">super&lt;/span>.onLayout(changed, left, top, right, bottom); mWidth = getWidth(); mHight = getHeight(); } &lt;span class="hljs-annotation">@Override&lt;/span> &lt;span class="hljs-keyword">protected&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">onDraw&lt;/span>(Canvas canvas) { &lt;span class="hljs-keyword">super&lt;/span>.onDraw(canvas); initPosition(); drawXYTitle(canvas); drawXYLine(canvas); drawContent(canvas); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initPosition&lt;/span>() { &lt;span class="hljs-comment">//初始化坐标图的xy交点原点坐标&lt;/span> mZeroPos[&lt;span class="hljs-number">0&lt;/span>] = mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>; mZeroPos[&lt;span class="hljs-number">1&lt;/span>] = mHight - mXYTitleTextSize * &lt;span class="hljs-number">4&lt;/span>; &lt;span class="hljs-comment">//初始化坐标图的X轴最大值坐标&lt;/span> mMaxXPos[&lt;span class="hljs-number">0&lt;/span>] = mWidth; mMaxXPos[&lt;span class="hljs-number">1&lt;/span>] = mHight - mXYTitleTextSize * &lt;span class="hljs-number">4&lt;/span>; &lt;span class="hljs-comment">//初始化坐标图的Y轴最大值坐标&lt;/span> mMaxYPos[&lt;span class="hljs-number">0&lt;/span>] = mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>; mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] = mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>; } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawXYTitle&lt;/span>(Canvas canvas) { mPaint.setColor(Color.parseColor(&lt;span class="hljs-string">"#1FB0E7"&lt;/span>)); mPaint.setTextSize(mXYTitleTextSize); mPaint.setTextAlign(Paint.Align.LEFT); &lt;span class="hljs-comment">//画Y轴顶的title&lt;/span> canvas.drawText(mTitleY, mMaxYPos[&lt;span class="hljs-number">0&lt;/span>] - mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>, mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] - mXYTitleTextSize, mPaint); mPaint.setTextAlign(Paint.Align.RIGHT); &lt;span class="hljs-comment">//画X轴顶的title&lt;/span> canvas.drawText(mTitleX, mMaxXPos[&lt;span class="hljs-number">0&lt;/span>], mMaxXPos[&lt;span class="hljs-number">1&lt;/span>] + mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>, mPaint); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawXYLine&lt;/span>(Canvas canvas) { mPaint.setColor(Color.DKGRAY); mPaint.setTextAlign(Paint.Align.RIGHT); &lt;span class="hljs-comment">//画XY轴&lt;/span> canvas.drawLine(mMaxYPos[&lt;span class="hljs-number">0&lt;/span>], mMaxYPos[&lt;span class="hljs-number">1&lt;/span>], mZeroPos[&lt;span class="hljs-number">0&lt;/span>], mZeroPos[&lt;span class="hljs-number">1&lt;/span>], mPaint); canvas.drawLine(mZeroPos[&lt;span class="hljs-number">0&lt;/span>], mZeroPos[&lt;span class="hljs-number">1&lt;/span>], mMaxXPos[&lt;span class="hljs-number">0&lt;/span>], mMaxXPos[&lt;span class="hljs-number">1&lt;/span>], mPaint); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawContent&lt;/span>(Canvas canvas) { mGridLevel = mXLevel.size() - &lt;span class="hljs-number">1&lt;/span>; &lt;span class="hljs-comment">//计算出偏移title等显示尺标后的真实XY轴长度,便于接下来等分&lt;/span> mRealWidth = (mWidth - mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>); mRealHight = (mHight - mXYTitleTextSize * &lt;span class="hljs-number">4&lt;/span>); &lt;span class="hljs-comment">//算出等分间距&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> offsetX = mRealWidth/(mGridLevel); &lt;span class="hljs-keyword">int&lt;/span> offsetY = mRealHight/(mGridLevel+&lt;span class="hljs-number">1&lt;/span>); &lt;span class="hljs-comment">//循环绘制content&lt;/span> &lt;span class="hljs-keyword">for&lt;/span> (&lt;span class="hljs-keyword">int&lt;/span> index=&lt;span class="hljs-number">0&lt;/span>; index&lt;mGridLevel+&lt;span class="hljs-number">1&lt;/span>; index++) { mPaint.setColor(Color.DKGRAY); mPaint.setTextAlign(Paint.Align.RIGHT); mPaint.setTextSize(mXYTitleTextSize-&lt;span class="hljs-number">5&lt;/span>); &lt;span class="hljs-comment">//绘制X轴的那些坐标区间点,包含0点坐标&lt;/span> canvas.drawText(String.valueOf(mXLevel.get(index)), mZeroPos[&lt;span class="hljs-number">0&lt;/span>]+(index*offsetX), mZeroPos[&lt;span class="hljs-number">1&lt;/span>] + mXYTitleTextSize, mPaint); &lt;span class="hljs-keyword">if&lt;/span> (index != &lt;span class="hljs-number">0&lt;/span>) { &lt;span class="hljs-comment">//绘制Y轴坐标区间点,不包含0点坐标,X轴已经画过了&lt;/span> canvas.drawText(String.valueOf(mYLevel.get(index)), mZeroPos[&lt;span class="hljs-number">0&lt;/span>], mZeroPos[&lt;span class="hljs-number">1&lt;/span>]-(index*offsetY), mPaint); } &lt;span class="hljs-keyword">if&lt;/span> (index == mGridLevel) { &lt;span class="hljs-comment">//坐标区间 = 真实区间 + 1&lt;/span> &lt;span class="hljs-keyword">break&lt;/span>; } mPaint.setColor(mGridColorLevel.get(mGridLevel - &lt;span class="hljs-number">1&lt;/span> - index)); mPaint.setStyle(Paint.Style.FILL); &lt;span class="hljs-comment">//绘制区间叠加图谱方块,从远到0坐标,因为小的图会覆盖大的图&lt;/span> canvas.drawRect(mMaxYPos[&lt;span class="hljs-number">0&lt;/span>], mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] + index*offsetY, mMaxXPos[&lt;span class="hljs-number">0&lt;/span>]-index*offsetX, mMaxXPos[&lt;span class="hljs-number">1&lt;/span>], mPaint); mPaint.setColor(mGridTxtColorLevel.get(index)); mPaint.setTextAlign(Paint.Align.RIGHT); mPaint.setTextSize(mXYTitleTextSize); &lt;span class="hljs-comment">//绘制每个方块状态区间的提示文字&lt;/span> canvas.drawText(mGridLevelText.get(index), mMaxXPos[&lt;span class="hljs-number">0&lt;/span>] - index * offsetX - mXYTitleTextSize, mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] + index * offsetY + mXYTitleTextSize, mPaint); } &lt;span class="hljs-comment">//绘制当前坐标&lt;/span> drawNotice(canvas, offsetX, offsetY); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawNotice&lt;/span>(Canvas canvas, &lt;span class="hljs-keyword">int&lt;/span> offsetX, &lt;span class="hljs-keyword">int&lt;/span> offsetY) { &lt;span class="hljs-keyword">int&lt;/span> realPosX = &lt;span class="hljs-number">0&lt;/span>; &lt;span class="hljs-keyword">int&lt;/span> realPosY = &lt;span class="hljs-number">0&lt;/span>; &lt;span class="hljs-comment">//计算传入的x值与真实屏幕坐标的像素值的百分比差值转换&lt;/span> &lt;span class="hljs-keyword">for&lt;/span> (&lt;span class="hljs-keyword">int&lt;/span> index=&lt;span class="hljs-number">0&lt;/span>; index&lt;mGridLevel; index++) { &lt;span class="hljs-keyword">if&lt;/span> (mMeasureXpos &gt;= mXLevel.get(index) && mMeasureXpos &lt; mXLevel.get(index+&lt;span class="hljs-number">1&lt;/span>)) { &lt;span class="hljs-keyword">int&lt;/span> subValue = mMeasureXpos - mXLevel.get(index); &lt;span class="hljs-keyword">int&lt;/span> offset = mXLevel.get(index+&lt;span class="hljs-number">1&lt;/span>) - mXLevel.get(index); realPosX = mZeroPos[&lt;span class="hljs-number">0&lt;/span>] + index*offsetX + (subValue / offset); &lt;span class="hljs-keyword">break&lt;/span>; } } &lt;span class="hljs-comment">//计算传入的y值与真实屏幕坐标的像素值的百分比差值转换&lt;/span> &lt;span class="hljs-keyword">for&lt;/span> (&lt;span class="hljs-keyword">int&lt;/span> index=&lt;span class="hljs-number">0&lt;/span>; index&lt;mGridLevel; index++) { &lt;span class="hljs-keyword">if&lt;/span> (mMeasureYpos &gt;= mYLevel.get(index) && mMeasureYpos &lt; mYLevel.get(index+&lt;span class="hljs-number">1&lt;/span>)) { &lt;span class="hljs-keyword">int&lt;/span> subValue = mMeasureYpos - mYLevel.get(index); &lt;span class="hljs-keyword">int&lt;/span> offset = mYLevel.get(index+&lt;span class="hljs-number">1&lt;/span>) - mYLevel.get(index); realPosY = mZeroPos[&lt;span class="hljs-number">1&lt;/span>] - index*offsetY - (offsetY - (subValue / offset)); &lt;span class="hljs-keyword">break&lt;/span>; } } &lt;span class="hljs-comment">//画我们传入的坐标点的标记小红点&lt;/span> mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(realPosX, realPosY, &lt;span class="hljs-number">8&lt;/span>, mPaint); &lt;span class="hljs-keyword">int&lt;/span>[] centerPos = {mZeroPos[&lt;span class="hljs-number">0&lt;/span>] + mRealWidth/&lt;span class="hljs-number">2&lt;/span>, mZeroPos[&lt;span class="hljs-number">1&lt;/span>] - mRealHight/&lt;span class="hljs-number">2&lt;/span>}; mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); RectF rectF = &lt;span class="hljs-keyword">null&lt;/span>; Path path = &lt;span class="hljs-keyword">new&lt;/span> Path(); &lt;span class="hljs-comment">//画红点旁边的提示框和文字,有四个区域,然后提示框的小三角指标方位不同&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &lt;= centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &gt;= centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//left-bottom&lt;/span> &lt;span class="hljs-comment">//画三角形&lt;/span> path.moveTo(realPosX+&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY-&lt;span class="hljs-number">15&lt;/span>); &lt;span class="hljs-comment">//画矩形背景&lt;/span> rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY-&lt;span class="hljs-number">40&lt;/span>, realPosX+&lt;span class="hljs-number">200&lt;/span>, realPosY + &lt;span class="hljs-number">30&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); &lt;span class="hljs-comment">//画提示框的文字&lt;/span> mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX+&lt;span class="hljs-number">30&lt;/span>, realPosY, mPaint); } &lt;span class="hljs-keyword">else&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &lt;= centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &lt; centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//left-top&lt;/span> path.moveTo(realPosX+&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX + &lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">15&lt;/span>); rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">20&lt;/span>, realPosX+&lt;span class="hljs-number">200&lt;/span>, realPosY + &lt;span class="hljs-number">50&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX+&lt;span class="hljs-number">30&lt;/span>, realPosY+&lt;span class="hljs-number">20&lt;/span>, mPaint); } &lt;span class="hljs-keyword">else&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &gt; centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &gt;= centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//right-bottom&lt;/span> path.moveTo(realPosX-&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX - &lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">15&lt;/span>); rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX-&lt;span class="hljs-number">200&lt;/span>, realPosY-&lt;span class="hljs-number">40&lt;/span>, realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY + &lt;span class="hljs-number">30&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX-&lt;span class="hljs-number">180&lt;/span>, realPosY, mPaint); } &lt;span class="hljs-keyword">else&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &gt; centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &lt; centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//right-top&lt;/span> path.moveTo(realPosX-&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX - &lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">15&lt;/span>); rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX-&lt;span class="hljs-number">200&lt;/span>, realPosY - &lt;span class="hljs-number">20&lt;/span>, realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY + &lt;span class="hljs-number">50&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX-&lt;span class="hljs-number">180&lt;/span>, realPosY+&lt;span class="hljs-number">30&lt;/span>, mPaint); } path.close(); mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); canvas.drawPath(path, mPaint); } &lt;span class="hljs-comment">//设置当前比值&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">updateValues&lt;/span>(&lt;span class="hljs-keyword">int&lt;/span> x, &lt;span class="hljs-keyword">int&lt;/span> y) { mMeasureXpos = x; mMeasureYpos = y; postInvalidate(); } &lt;span class="hljs-comment">//设置XY轴顶角的title字体大小&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">setTitleTextSize&lt;/span>(&lt;span class="hljs-keyword">int&lt;/span> size) { mXYTitleTextSize = size; } &lt;span class="hljs-comment">//初始化X轴的坐标区间点值,可以不均等分&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initXLevelOffset&lt;/span>(ArrayList&lt;Integer&gt; list) { mXLevel.clear(); mXLevel.addAll(list); } &lt;span class="hljs-comment">//初始化Y轴的坐标区间点值,可以不均等分&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initYLevelOffset&lt;/span>(ArrayList&lt;Integer&gt; list) { mYLevel.clear(); mYLevel.addAll(list); } &lt;span class="hljs-comment">//初始化每个区间的提示文字,如果不想显示可以设置""&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initGridLevelText&lt;/span>(ArrayList&lt;String&gt; list) { mGridLevelText.clear(); mGridLevelText.addAll(list); } &lt;span class="hljs-comment">//初始化每个区间的颜色&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initGridColorLevel&lt;/span>(ArrayList&lt;Integer&gt; list) { mGridColorLevel.clear(); mGridColorLevel.addAll(list); } &lt;span class="hljs-comment">//初始化每个区间的提示文字颜色&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initGridTxtColorLevel&lt;/span>(ArrayList&lt;Integer&gt; list) { mGridTxtColorLevel.clear(); mGridTxtColorLevel.addAll(list); } &lt;span class="hljs-comment">//初始化XY轴title&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initTitleXY&lt;/span>(String x, String y) { mTitleX = x; mTitleY = y; } }` 再来看下布局文件: ...

2015年6月6日 · 7 分钟 · 天边的星星