1. 熟悉Picasso、Glide、Universal-Image-Loader等主流的图片加载处理框架,了解三者的优缺点,能够针对不同的情况将合适的框架运用到实际开发当中。
Picasso是一款开源的比较早的图片加载类库,支持加载多种来源的图片,比如网络,sd卡,res资源;可以设置占位图,也支持对图片的自定义处理;另外在回收imageview时,如果imageview中有下载进程,它会将这个下载进程给取消掉;
Glide是一款专注于处理平滑滑动的图片加载类库,底层默认使用httpURLconnection来加载图片,当然也可以设置为okhttp或volley;
Glide与Picasso相比,Glide可以加载GIF动态图,并且由于glide的缓存默认是根据imageview控件的宽高进行的,并不像Picasso默认加载整个图片,因此glide加载图片的速度要优于Picasso;但由于glide默认的Bitmap格式是RGB_565,所以在图片的质量上要比Picasso差一些(但单凭肉眼难以分辨) ---gif、Video,如果你们是做美拍、爱拍这种视频类应用,建议使用glide,如果对app大小很在意可以考虑Picasso
Universal-Image-Loader拥有丰富的配置项,如线程池,下载器,缓存策略,缓存文件名称生成器等,它支持加载多种来源的图片,如sd卡,网络,uri,asset,res目录,同时它也支持给加载图片添加动画和圆角图片;使用时需要先初始化Universal-Image-loader,一般在application的onCreate中初始化,并配置一下options;配置完之后使用起来十分方便简单,这也是我个人比较喜欢的一个类库.
2. 熟悉icon、Volley、Retrofit等主流的网络加载框架,了解三者的优缺点,能够对其进行相应的封装,针对不同的情况将合适的框架运用到实际开发当中。
ion是一个异步的网格请求框架与图片加载框架,底层是通过socket来实现的,其中在它的核心对象ion中封装了子线程与http的请求细节,向服务端发送请求后在它的FutureCallback回调中接收服务端返回的数据,在onCompleted方法中根据接收的数据进行相应的处理;
Volley是在13年Google I/O大会上推出的一款网络请求和图片加载框架,它内部使用HttpURLConnection和HttpClient来进行网络请求,只是在底层对于不同的Android版本进行了响应的切换,2.3之前使用的HttpClient,2.3之后使用的是HttpURLConnection
它非常适合那种数据量不大但是通信频繁的网络请求,但它对于大数据量的操作,如文本下载,表现则没有那么好,同时它本身也没有实现图片的三级缓存;此外,当 当用户finish当前的Activity的时候,而当前Activity正在进行网络请求,Volley支持取消网络请求,这样可以减少内存泄漏,减少用户流量的消耗(写在onDestroy方法中);
创建请求队列RequestQueue-->根据返回值决定使用哪个请求类去进行网络请求-->在listener回调中接收请求成功后返回的数据,在errorlistener中接收请求失败或异常信息-->最后将请求添加到请求队列中发送请求; 当然通常还要对volley进行一定封装后再进行使用;
Retrofit相对来说是性能最好的一款网络请求框架,和我们之前的所接触的框架不同,它通过使用注解配置请求相关的参数,减少了代码量,但相对其他框架来说有点难理解;它底层网络请求默认采用okhttp,当然也可以和volley这些主流框架进行切换
3. 熟悉LruCache以及线程池的底层实现原理。---实战技巧
LruCache是一个实现图片内存缓存的类,采用Lru删除最近最少使用的算法;使用它必须要实现sizeOf方法,用来指定每条数据的size,(此处是返回bitmap的大小)它内部通过一个按照访问顺序排序的LinkedHashMap来存储数据,每次当缓存命中的时候,会将该条数据移到上方,并会判断当前缓存size是否超出了最大缓存maxSize(谷歌推荐的是app可用内存的1/8),如果超出则移除最下方也就是最少使用的数据;
least recentlly use 最少最近使用算法会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定;
如果问你加载图片的原理:
在加载图片中使用了三级缓存,在处理图片内部缓存的时候采用的是LRUcache,在处理图片加载任务的时候采用线程池来管理;然后再将这三方面扩展一下讲
三级缓存:首次加载app时,通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中(为了避免内存溢出的问题,需要对图片进行压缩理,inSampleSize),之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片,将图片保存到本地时图片名用MD5加密来实现(在没退出app前若想要加载最新数据,则通过上拉刷新下拉加载手动来获取)
4. 熟悉Android中常见的内存泄露的引发原因以及内存优化的方案。
内存溢出: 你要求分配的内存也就是你需要的内存,超出了(它)可用的最大内存
内存泄漏: 简单粗俗的讲,就是本该被释放的对象没有释放,一直被某个或某些实例所持有占据着内存但却不再被再次使用,导致 GC 不能回收。
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。比如:如果你在单例模式中传入一个context上下文,如果这个这个上下文是application的上下文还好,因为它和应用的生命周期一样,不会导致内存溢出;但如果传入的是一个activity的上下文,那么当这个activity被销毁时,由于这个context的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以它的内存并不会被回收,这就造成泄漏了。--因此上下文要使用application的上下文
在开启activity或显示dialog时须用activity的上下文,其余什么上下文均可!!!
非静态内部类创建静态实例造成的内存泄漏:
静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法),只能访问外部类的静态成员(包括静态变量和静态方法)
有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法:
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}
}
这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的 Context。
资源未关闭造成的内存泄漏:
对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
由Handler引起的内存泄漏:
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
在上面的代码中当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用,而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)或延迟操作一起出现,所以如果用户在网络请求过程中或延时消息得到处理前关闭了Activity,那么因为这条消息持有对handler的引用,而handler又持有对其外部类(在这里,即SampleActivity)的潜在引用。而这条引用关系会一直保持 直到网络请求完毕或延迟消息得到处理,而这阻止了SampleActivity被垃圾回收器回收,同时造成应用程序的泄漏。
Handler内存泄漏的解决方案:
方法一:当销毁activity的时候,在activity的onStop或onDestroy方法中调用Handler的removeCallbacks(Runnable)或removeMessages(int what)或直接mHandler.removeCallbacksAndMessages(null);方法,把消息对象从消息队列移除就行了
方法二:将Handler声明为静态类,同时通过弱引用的方式引入 Activity
加载图片不经压缩处理直接加载大图,Listview 如果不复用缓存或通过viewholder来减少findviewbyId的操作在很大程度上都可能早成内存溢出;
避免OOM(内存溢出):
A.避免使用枚举
B.Bitmap对象是很消耗内存的,所以我们在加载图片的时候要通过 inSampleSize为图片设置一个合适的缩放比,避免直接加载大图;解码格式如果没有什么特殊需求,应使用RGB565而不是ARGB8888
C.在对图片进行压缩处理的同时,还要提高复用性,如使用LRUCache 内存缓存,只保留最近最常使用图片
D.另外在一些会被频繁调用的方法(值动画的监听方法,onDraw方法,getView方法)中,要尽量避免在其中new对象,这样会迅速增加内存的使用
5. 熟悉Handler消息传递机制的应用和原理,能够通过Handler来实现线程间的通讯。
因为在主线程队列中不能处理耗时操作,超过5秒就会报ANR异常,所以通常通过在子线程中处理耗时操作,将结果通过消息发送给handler,handler在主线程中接收到消息,就会通过handlerMessage方法进行相应的处理;
这时handler使用的一个大致流程,其实当系统跑起来的时候,在主线程中就会创建出MessageQueue Looper对象,这也是我们使用handler的时候不用手动创建MessageQueue Looper的原因,然后为了保证一个线程只对应一个loop对象,一个loop只对应一个MessageQueue队列,系统在创建loop的时候会通过ThreadLocal(线程级单例)把它和当前线程绑定在一起,同样创建messagequeue的时候会通过一个final类型的成员变量将其保存起来,这样就实现了上面所说的;
它通过 Looper.loop() 方法去取消息,在这个方法中设置了一个死循环来让looper不断的去消息队列中查看有没有消息,如果有的话,就将消息取出来,然后会调用handler 的dispatchMessage方法来分发消息,因为message中可能有回调,handler在创建的的时候也可能会给它传一个callback,因此在这个方法中会做一个判断,判断handler和message在构造的时候是否传入了callback,只有当这两个回调都为空的时候,才会执行handMessage方法,去处理消息;否则则交给回调函数处理;
Loop中死循环不会对主线程造成影响,因为对于主线程来说我们肯定不希望,它运行一段时间,执行完一段代码后程序就自己退出了,这样会造成很不好的用户体验,我们肯定希望它可以一直活着,而loop中的死循环便能保证主线程不会被退出,无消息时会挂起休眠,当收到消息的时候又会被重新唤醒;
下面可不用说:
向消息队列发送消息 handler sendMessageAtTime()-> messqueue.enqueuemessage() 消息排序(通过消息要执行的时间进行排序) Message.next() 有消息需要马上执行 调用nativewake()->把Looper.looper() 唤醒 queue.next()开始工作````
6. 熟悉Android中通讯的实现,包括四大组件之间的通讯、fragment之间的通讯、进程间的通讯以及线程间的通讯。
线程间通信: Handler
进程间通信: Activity可以跨进程调用其他应用程序的Activity,比如调用系统打电话,发短信的进程;Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以去监听这些广播,收到广播后,可以做出相应的操作,是一种被动的通信方式;Content Provider可以把应用的私有数据共享给其他应用访问,所以可以实现进程间通信;
AIDL(Android Interface Define Language),是android的一种接口定义语言,可以定义接口,使得客户端和服务端之间实现进程间通信
Activity与fragment之间的通讯:
Fragment可以调用getActivity()方法很容易的得到它所在的activity的对象,然后就可以查找activity中的控件们(findViewById())。例如:
ViewlistView =getActivity().findViewById(R.id.list);同样的,activity也可以通过FragmentManager的方法查找它所包含的frament们。例如:
[java] view plain copy
1. ExampleFragment fragment =(ExampleFragment)getFragmentManager().findFragmentById(R.id.example_fragment
有时,你可能需要fragment与activity共享事件。一个好办法是在fragment中定义一个回调接口,然后在activity中实现之或者在fragment中定义含参的set,get方法,在activity中传入参数调用方法;也可以反过来;
7. 熟悉Android中的Json的解析,熟悉Gson框架的使用,并对XML的解析及XStream框架的使用有一定的了解。
8. 熟悉Android中的事件分发机制,熟悉常见的事件冲突解决方案。
9. 熟悉常用的UI框架,能够熟练的完成UI界面的搭建。
10. 屏幕适配
于五种主流的像素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)应按照 2:3:4:6:8 的比例进行缩放。例如,一个启动图标的尺寸为48x48 dp,这表示在 MDPI 的屏幕上其实际尺寸应为 48x48 px,在 HDPI 的屏幕上其实际大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的屏幕上其实际大小是 MDPI 的 2 倍 (96x96 px),依此类推。
11. 熟悉Android原生的SwipeRefreshLayout控件和第三方的PullToRefresh框架,来实现对ListView、RecyclerView的下拉刷新和上拉加载功能的实现。
12. 熟悉Android中自定义控件,自定义动画的实现(一定程度上),能够根据具体的要求来实现相应的控件效果。
13. 熟悉WebView的使用。
14. 熟悉APK的打包、加密以及上市流程。
15. 熟悉推送、登陆、支付等常见的第三方sdk的使用。
16. 熟悉常用的版本控制工具。
17. 熟悉常见的加密方式(如:RSA、 MD5、 DES,AES、 SHA1、Base64 等)。
URL编码:http协议中请求的url不支持中文和特殊字符(如&?),所以需要对url进行编码和解码,编码使用的是URLEncoder,解码使用的是
Base64:可以将二进制数据(图片视频都是以二进制的形式存储)对象转为字符串(文本格式),主要用于在http传输中将一些比较大的比如二进制数据,编码为字符串,适合放入url进行传递,而且具有非明文行
sharedpreference本身只能够存储基本数据类型,不能存储自定义对象,但我们可以将对象进行base64编码后,间接存储到sharedpreference中,取出时再对其解码
数字摘要:是指通过算法将长数据变为短数据,通常用来标识数据的唯一性,以及数据是否被修改,常用的加密算法有md5和sha1,如安卓app的签名也是用这2种算法计算的;
md5由于具有不可逆性,也被用来作为密码加密,并且通常情况下为了让加密过程变的不可预测,我们会进行加盐操作
sha1也不可逆,比md5长度更长,所以更安全,但是加密的效率比md5要慢一些,如文件的秒传功能,(比如你想给你好友传一文件,上传时会系统会匹配本地文件的sha1值,是否和服务器上的某个文件相同,就是服务器是是否有相同的文件,有就直接从服务器发给你好友,就不用你在上传了),以及相同的v4包冲突都是根据文件的sha1值进行比对的(sha1值不同报冲突,相同就不报了)
·
加密和解密,一般分为对称加密和非对称加密
·
·
对称加密:
·
o 密钥可以自己指定,只有一把密钥,如果密钥暴露,文件就会被暴露,
o 常见对称加密算法有DES算法(Data Encryption Standard)和AES算法(Advanced Encryption Standard),AES比DES安全一点,相对的效率要低一点
o 特点是加密速度很快,但是缺点是安全性较低,因为只要密钥暴漏,数据就可以被解密了。
· 非对称加密:有两把钥匙(密钥对),公钥和私钥,公钥的话给别人,私钥自己保存
o 常见非对称加密算法是RSA
o 2把密钥通常是通过程序生成,不能自己指定
o 特点是加密速度慢些,但是安全系数很高
o 加密和解密的规则是:公钥加密只能私钥解密,私钥加密只能公钥解密,只能两者配合使用
o 应用场景举例:在集成支付宝支付sdk时,需要生成私钥和公钥,公钥需要设置到支付宝网站的管理后台,在程序中调用支付接口的时候,使用我们自己的私钥进行加密,这样支付宝由于有公钥可以解密,其他人即时劫持了数据,但是没有公钥,也无法解密。
· 代码实践,使用hmAndroidUtils中的工具类进行操作
18. 熟悉android 5.0 和android 6.0的新特性,并可以运用到项目中
18. 熟练掌握Android中的常见机制,比如说事件分发和拦截机制、Handler机制等等
19. 熟练掌握自定义控件,能实现常用的UI效果
20. 熟练掌握代码复用,基类的抽取,优化代码结构,提高运行效率。
21. 了解JNI开发的基本流程。
22. 了解常用的设计模式,如工厂模式、观察者模式、模版模式等。
23. shareSDK分享
登录官网--》进入后台,添加应用,生成appk--》下载SDK,可以选择【】要集成的平台--》解压压缩包,打开快速集成的jar文件,--》弹出一个对话框,输入你你的项目名称,和你应用的包名(需要到项目清单文件中去复制),以及选择要集成的平台--》生成一个文件夹--》将文件夹中的内容全部copy到项目中--》根据集成文档,配置清单文件、activity(直接copy集成文档中的相应内容就好)--》用之前生成的appk,将项目assets目录下的shareSDK.xml中的sharedSDK节点下的appk给替换掉,其他的appk不用替换--》在代码中给分享按钮设置点击事件,将集成文档中,分享的代码copy过来直接用就可以
24. 极光推送 ---day06
消息推送
· 1.消息拉取,并不是真正的消息推送,它是通过一个死循环,设置计时器,每隔一段时间,就向服务器发送请求,如果有最新更新的信息,服务器就返回给客户端,虽然能实现类似消息推送的效果,但一直执行会费电,费流量,并且得到的最新消息有一定的延时,导致消息不及时(和服务器建立的是无状态的链接,请求完成后,链接就断开了)
· 2.真正的消息推送: 客户端会与服务器建立一个长连接,但如果是用数据链接的网络的话,当连上基站后,它一般会判断,如果5分钟以内没有数据,基站就会回收掉你占用的端口,这时长连接就会断掉;因此通常需要通过心跳(隔一段时间随便发送一个或两个字节的数据包)来维持长连接;服务器中会有一个集合,保存了所有与之建立长连接的客户端的IP,当要推送消息的时候,服务端,就会遍历集合将消息发送给所有客户端
· 极光推送:客户端先与极光推送的服务器建立长连接-->服务器端通过极光推送提供的API将消息发送给极光推送的服务器-->极光推送的服务器再将消息转发给客户端
· 即时通讯:两个想要相互通信的客户端都需要通过心跳与服务端建立长连接,然后发送消息的通过from 或 to 来标识发送方或接收方,from 代表发送消息的人,也就是自己; to 代表接收消息的人,也就是要发送给谁; 服务端受到消息后,根据from 或 to ,遍历集合(集合里保存了所有与之建立长连接的客户端的IP),将收到的相应的消息转发给 to 所指定的IP;如果 to 所指定的IP 不在线,则服务端会暂时缓存要转发的信息
· 视频会议: a先给服务器发一个包,标明邀请谁来参加视频会议,服务器,遍历集合,找到相应的IP,如果b,c 在线就将消息发送过去;b,c,点击接受后,a 就会将视频发给服务器,服务器再将视频转发给b,c
步骤:
进入官网--.>创建应用,指定应用包名(和清单文件保持一致)-->文档,资源下载,下载SDK--》解压压缩包,里面提供了一个例子,打开example的xml文件--》根据它将项目的清单文件进行相应的替换--》根据中文提示,在适当位置将包名和appk替换为项目包名和之前生成的appk--》到解压后的文件夹的lib目录下将arm和x86的文件夹,以及jpush-android.jar, copy到项目的libs文件夹下(如果要还需要适配其他的CPU类型,再将其他相应的文件夹copy过来即可)-->创建一个广播,用来接收信息,将广播配置到清单文件的server标签中--》初始化API,在application类的oncreate方法中,将集成文档中的初始化代码copy过来--》进入个人后台,选中应用,点击推送--》在推送界面就可以模拟一下服务端向客户端推送消息--》客户端收到推送的消息后,会在屏幕的下拉条中展示,当用户点击该消息的时候,触发广播-->进而执行广播中的代码,根据集成文档中的高级功能部分,查看怎样解析intent获取推送的消息
25. 了解主流的架构模式,了解MVP,MVC,MVVM等模式在实际项目中的应用
26. 轮播图红点的移动
27. 一开始就通过slidingmenu将界面分为contentfragment和 leftfragment两部分,在contentfragment中底部使用radiogroup,上面是一个填充剩下布局的viewpager,给viewpager设置适配器展示数据,展示的数是一个个pager,,contentfragment中的inITview负责加载要展示界面的布局(界面公共的部分),initdata负责加载将要展示 的界面pager添加到集合中,通过viewpager的适配器进行展示,其中pager的inITview的加载的是标题栏(直接在父类中实现了),子类的pager中只需要加载要展示界面的数据即可initdata
通过drawlayout来布局,在mainactivity中找到viewpager,给viewpager设置适配器,适配器中展示的数据是一个个fragment,适配器继承fragmentpageradapter(getCount,getItem,getPageTitle),在fragment的基类中通过loadpager来加载界面和数据,对外提供inITview和initdata的方法,所以之后每个具体的fragment只要实现这两个方法就好了,通过再次设置适配器来展示initdata加载的数据
多种条目布局:(对谷歌市场中的多条目布局进行适当的修改)
服务端传数据的时候,要传入该条目的类型type;客户端接收到数据后,编写been,遍历从服务器获取的数据,分别将获取条目类型type和条目内容添加到一个新建的集合中;展示数据时,通过position获取集合中每个位置的数据,并通过switch和之前代码中定义的条目类型进行比对,匹配后返回对应的条目类型,根据条目类型来加载不同的布局
重写baseAdapter的getViewCount,getItemViewType方法
谷歌市场热门界面,有自定义布局和动画,详情界面值得一看;
/**
* 线程池管理的封装:
* 精通java线程池的使用
* 1.java还提供了封装好的方便我们创建线程池的类和方法:
* Executors.newFixedThreadPool(nThreads);
* Executors.newCachedThreadPool(threadFactory);
* Executors.newSingleThreadExecutor();
* 2.我们也可以自定义线程池的配置,使用ThreadPoolExecutor自定义,如下:
*/
public class ThreadPoolManager {
private int corePoolSize;//核心线程池的大小,就是指能够同时执行的任务数量
private int maximumPoolSize;//最大线程池的大小,线程池数量的上限
private int keepAliveTime = 1;//存活时间
private TimeUnit unit = TimeUnit.HOURS;//时间单位
private ThreadPoolExecutor threadPoolExecutor;
private static ThreadPoolManager threadPoolManager = new ThreadPoolManager();
public static ThreadPoolManager getInstance() {
return threadPoolManager;
}
//通过构造函数进行初始化
private ThreadPoolManager() {
//计算核心线程池的大小:设备的可用处理器核心数*2 + 1,该数量的线程能够让cpu的效率得到最大发挥
corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
maximumPoolSize = corePoolSize; //最大线程池数量不要小于核心线程池数量
//线程池的处理机制:优先核心线程池->缓冲队列->最大线程池->拒绝线程的策略
threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize, //3
maximumPoolSize,//10,当缓冲队列满的时候,会判断最大线程池 ,注意:最大线程池的大小是包含核心线程池大小的
keepAliveTime, //表示的最大线程池中等待任务的存活时间
unit,
new LinkedBlockingQueue(5), //缓冲队列,当核心线程池满的时候,会将任务存放到缓冲队列里等着
Executors.defaultThreadFactory(), //创建线程的工厂
new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝线程的处理策略,CallerRunsPolic将当前线程移除
}
/**
* 添加任务
*/
public void execute(Runnable runnable) {
if (runnable != null) {
threadPoolExecutor.execute(runnable);
}
}
/**
* 将任务从线程池中移除,如果任务正在执行,会先终止线程,再移除
*/
public void remove(Runnable runnable) {
if (runnable != null) {
threadPoolExecutor.remove(runnable);
}
}
//请求网络获取数据
getDataFromServer();
/**
* 适配器不能再这里设置,因为getDataFromServer中包含请求网络的操作,异步操作,导致设置适配器的代码和getDataFromServer
* 平级,所以适配器中需要的数据,getDataFromServer还没有返回,-->导致空指针异常!!!
*/
/*//展示轮播图(图片),为viewpager设置适配器
mViewPager.setAdapter(new Myadapter());*/
轮播图实现:
布局:最外层套一个相对布局,指定高度--》放一个viewpager,和外层布局等高--》再放一个相对布局--》在最下方的左边放一个图片描述信息--》在最下方的右边放一个线性布局,当做一个容器,用来放点;
或者在先对布局中设置好容器和红点的位置(右下方),然后先放容器,在放imageview红点,让红点覆盖 可以覆盖掉容器中的一个白点;
代码实现:添加点时要么采用引导动画的方式,有几张图片就通过创建view,创建几个点,点的背景图片直接设置为白点,通过:
LinearLayout.LayoutParams params = new LayoutParams(5, 5);//设置点点宽高
params.rightMargin = 5;//设置点的右边距
view.setLayoutParams(params);//将设置好的点的属性值赋给bai点
来设置点的宽高等属性,然后添加到linearlayout容器中;--》为viewPager设置适配器展示图片--》设置OnPageChangeListener,在onPageScrolled 当界面切换时,计算红点的移动距离,然后先获取红点的属性,在将移动距离设置给红点来实现移动:
// 2.拿到红点当前的的属性
RelativeLayout.LayoutParams params =(RelativeLayout.LayoutParams)mRedPoint
.getLayoutParams();
// 3.重新设置(修改)该属性值
params.leftMargin =redPixels;
// 4.将修改后的属性值重新设置给红点,来实现红点的移动
mRedPoint.setLayoutParams(params);
要么,在添加点时,点的背景图片设置为状态选择器,通过setenable来设置显示红点还是白点--》为viewpager设置适配器展示图片,在container.addView(imageView);之后设置图片的触摸监听,根据图片的触摸状态判断是否继续轮播图片(OnTouchListener):
//设置图片的触摸监听,根据图片的触摸状态判断是否继续轮播图片
private class MyOnTouchListenerimplements OnTouchListener {
@Override
public boolean onTouch(Viewv, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//当按下图片的时候需要停止轮播,即删除已经发送的消息
hd.removeCallbacksAndMessages(null);//删除发送的消息
break;
case MotionEvent.ACTION_UP:
//当手指抬起的时候,继续轮播图片,即发送消息,继续切换下一张图片
hd.sendMessageDelayed(Message.obtain(), 2000);//发送延时信息
break;
case MotionEvent.ACTION_CANCEL:
//当事件取消的时候(按下控件后,不抬起,移出控件),继续轮播图片
hd.sendMessageDelayed(Message.obtain(), 2000);//发送延时信息
break;
default:
break;
}
// 返回false 孩子不处理事件 父容器就不传递其他的事件了,也就无法得到抬起或取消的事件,因此须返回true,表示孩子处理事件
return true;
}
--》因为轮播图上有相应的图片描述,所以为了根据图片显示出相应的描述信息,需要对viewpager设置界面滑动监听,onPagechangeListener ,在它的onPageSelected中,当前界面被选中,切换完成时,设置相应图片描述信息,并将之前的红点变白点,当前的白点变红点,并记录当前点:
@Override
public void onPageSelected(int position) {
//显示轮播图片的描述信息
mIconInfo.setText(tabDetailBeen.data.topnews.get(position).title);
//切换到该界面的时候先将之间的红点变成白点
mLayout.getChildAt(preRedPoint).setEnabled(false);
//切换到该界面后将该界面的点变红
mLayout.getChildAt(position).setEnabled(true);
//记录前一个红点的位置
preRedPoint = position;
}
--》最后发送消息切换轮播图:
//发送消息,切换轮播图
//因为当缓存或访问网络的时候都要发送消息,所以为了确保只发送一个消息,需要先清空之前未发送的消息
if (hd ==null) {
hd = new MyHandler();
}
//清空之前的还为来得及发送的消息
hd.removeCallbacksAndMessages(null);//null: 删除之前发送的所有消息,填入具体的消息则删除指定的消息
//发送延时消息
hd.sendMessageDelayed(Message.obtain(), 2000);
Handler中实现无限循环播放:
private class MyHandler extends Handler {
@Override
public void handleMessage(Messagemsg) {
//解决内存泄漏,判断当前ViewPager是否存在界面上,如果不显示就不再发消息了
if (mViewPager.getWindowVisibility() == View.GONE) {
hd.removeCallbacksAndMessages(null);
return;
}
//切换到最后一页时,需要重新切回第一页,所以对当前的mViewPager.getCurrentItem() + 1取模
int newIndex = (mViewPager.getCurrentItem() + 1) %tabDetailBeen.data.topnews.size();
//接收消息,切换轮播图,当即将切换到第一页的时候,不要切换动画
if (newIndex == 0) {
mViewPager.setCurrentItem(newIndex,false);//false: 不要切换动画
}else {
mViewPager.setCurrentItem(newIndex);
}
//再次发送消息,实现无限循环(即使不动也要发消息,使轮播图继续)
hd.sendMessageDelayed(Message.obtain(), 2000);
}
对viewpager的事件处理:
public TabDetailVeiwPager(Contextcontext, AttributeSet attrs) {
super(context,attrs);
}
/**
* 1、不处理上下滑动,让父容器拦截,getParent().requestDisallowInterceptTouchEvent(false);
* 2、左右滑动,自己处理事件,不让父容器拦截事件
* 2.1、第1页时,且手指从左往右,滑动到最后一页
* 2.2、最后一页时,且手指从右往左,滑动到第一页
getParent().requestDisallowInterceptTouchEvent(true);
*
*/
@Override
public boolean dispatchTouchEvent(MotionEventev) {
//请求父元素不拦截事件
getParent().requestDisallowInterceptTouchEvent(true);
// 1.不处理上下滑动
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = (int)ev.getX();
downY = (int)ev.getY();
//因为在安卓中像viewgroup这些控件拦截的都是move事件,至少会将down事件传下来,
//所以为了保证move事件可以执行,在down事件中应请求父控件不要拦截事件
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int)ev.getX();
int moveY = (int)ev.getY();
//获取移动的X,Y的距离,需要去绝对值!!!!!
int distanceX = Math.abs(moveX -downX);
int distanceY = Math.abs(moveY -downY);
if (distanceY >distanceX) {
//请求父控件拦截事件(针对的是上下滑动)
getParent().requestDisallowInterceptTouchEvent(false);
}else {
//左右滑动
//Int currentItem = -1;
//currentItem = getCurrentItem()+ 1 % getAdapter( ).getCount( );
// 2.1 如果是第一页,且由左向右滑动,当前页应变为最后一页
if (getCurrentItem() ==0 && (moveX -downX) > 0) {
setCurrentItem(getAdapter(). getCount()- 1);
//setCurrentItem(currentItem);
}else if (getCurrentItem() == getAdapter().getCount() - 1 && (moveX -downX) < 0 ) {
// 2.2 如果是最后一页,且由右向左滑动时,当前页应变为第一页 setCurrentItem(0);
}
// 2.3 其他情况,则需要请求父控件不拦截事件,由孩子(轮播图viewPager)来处理事件 getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return super.dispatchTouchEvent(ev);
}
·
4、Webview使用
/**
* @Description: 初始化网页
* @param:
*/
private void initWebView() {
String url = getIntent().getStringExtra("url");
System.out.println(url);
//配置webview
WebSettings settings = mWeb.getSettings();
settings.setBuiltInZoomControls(true);// 支持缩放按钮
settings.setUseWideViewPort(true);// 支持双击缩放
settings.setJavaScriptEnabled(true);// 支持JavaScript
//监听webview
mWeb.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
// 网页加载完成后回调
//网页加载完成后隐藏进度条
mProgress.setVisibility(View.GONE);
super.onPageFinished(view, url);
}
});
//加载URL
mWeb.loadUrl(url);
}
·
6、更改Webview字体大小:
private int currentIndex = 2;
private WebSettings settings;
private int tempIndex; //临时变量
private void showTextsize() {
AlertDialog.Builder builder = new Builder(this);
String[] items = new String[]{"超大号字体","大号字体","正常字体","小号字体","超小号字体"};
builder.setSingleChoiceItems(items, currentIndex, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//将选择的字体的索引先保存在临时变量中,只有点击确定的时候才保存
tempIndex = which;
}
});
builder.setPositiveButton("确定", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
currentIndex = tempIndex;
// 根据位置改变字体
switchTextSize(currentIndex);
}
});
builder.setNegativeButton("取消",null);
builder.show();
}
主线程向子线程发消息:(详见收藏)
--》创建一个类去继承Thread--》重写run方法--》在run方法里创建handler(子线程注意要手动创建looper对象和消息队列),调用handmessage方法处理收到的消息
--》主线程中创建该类的对象--》使用该对象通过其中的handler调用sendMessage方法发送消息
或者使用HandlerThread也可以,HandlerThread 继承自Thread,内部封装了Looper。
1. 创建一个HandlerThread,即创建了一个包含Looper的线程。
HandlerThread handlerThread = new HandlerThread("leochin.com");
handlerThread.start(); //创建HandlerThread后一定要记start()
2. 获取HandlerThread的Looper
Looper looper = handlerThread.getLooper();
3. 创建Handler,通过Looper初始化
Handler handler = new Handler(looper);
通过以上三步我们就成功创建HandlerThread。通过handler发送消息,就会在子线程中执行。
如果想让HandlerThread退出,则需要调用handlerThread.quit();。
reclyerView 的点击事件:
通过adapter中自己去提供回调的方式,在adapter中定义接口OnItemClickLitener,并定义两个抽象方法onItemClick(View view, int position);
onItemLongClick(View view , int position);--》在adapter的onBindViewHolder方法中对接口进行回调
--》使用这种方式可以将这部分相同的代码抽取到父类中去,这样只写一次就可以了
当然如果逻辑很简单的话,也可以直接在adapter中对条目布局设置监听
在或者可以通过对reclyerView设置addOnItemTouchListener的方式,只是这种方式之前没用过,不是很了解
EventBus:
EventBus主要解决线程间的通信,组件(四大组件)之间的通信:
首先定义一个事件(可以是任何的javabean)--》谁想接受这个事件,就在它的任意一个方法上(生命周期方法除外)加一个注解@Subscribe,这样就创建了一个订阅者,可以接收发过来的消息(接收消息还要注册订阅者,一般在oncreate或onstart方法中注册,在ondestory方法中解除注册)--》发消息,可以在你代码的任何一个地方执行发消息的代码