需求:实现图片按钮的需求。要求在一个按钮的实现图片加文本居中显示。
实现:自定义控件ImageButton。ImageButton继承了LinearLayout,加载了R.layout.ui_imagebutton_h
布局,R.layout.ui_imagebutton_h
里定义了一个ImageView
和TextView
,暴露设置文字,文字大小,文字颜色,图片等设置的接口。问题解决。
当初的思路有其他两种实现方式:
onDraw()
方法,绘制图片与文字。这种思路可以保留Button的系统定义的功能,不用我们自己再去写。但可能代码复杂度要高点,不如复合控件来得快。有时依项目要求要重写 onMeasure
onLayout()
方法来控制布局大小。android:drawableLeft
来实现图文混排。实际测试发现,无法实现图文居中显示,图与文字的距离无法控制,系统默认为图片在控件的最左边。可能需要我们自己复写 相应drawableLeft属性对应的方法。需求:圆形按钮的实现加点击效果的实现。
实现:实现圆形按钮就是为此建立一个drawable文件即可。同理,点击效果使用selector
即可。
需求:主页面需要在底部添加4个水平排列且均匀分布的选项按钮,选项按钮样式为图片在上,文字在下,同时单击选项按钮时,主页面的界面改变成相应的页面,按钮的图片颜色与文字都变化成指定的颜色。很常见的导航按钮。
实现:选项按钮的样式实现仍然采用复合型控件的方式,名OptionView
,其xml文件包括一个表示指示器的TextView
控件,一个显示图片的ImageView
,一个显示文字的TextView
,无难度。图片颜色的变化利用ColorMatrix
颜色矩阵。文字的颜色变化系统可以控制。 指示器的显示与否我选择控制其透明度来实现。在OptionView
类中,暴露一个change()
方法,使得可以同时改变指示器,图片,文字。图片颜色的变化用一个自定义方法handleImage(Bitmap bm, int value)
来处理。OptionView
中定义一个私有变量isChange()
,并暴露其get/setter方法。由于在此需求该控件只在xml引用,故只重写了public OptionView(Context context, AttributeSet attrs)
方法,并自己自定义所需的xml属性,来获得文字,变化的颜色,文字大小,需设置的图片源等。在主页面加载布局文件时,获得4个OptionView
的实例,为其添加单击事件监听。维护一个OptionView
实例的的全局变量mSelected
,表示该控件为当前选择控件,在主页面的onCreate
方法中将某一个OptionView
实例赋值给它同时调用它的change()
方法。而在单击事件中,只要判断当前选择的OptionView
控件是否为当前选择的控件,如果是,无作为,如果不是,则调用两者的change()
方法并改变mSelected
的值,接下来,就是Fragment的切换的问题了。需求实现。
单击事件的思路:最初实现按钮的单击事件时使用了复杂的观察者模式,让OptionView
实现一个接口A,接口B的实现类保存所有A接口的实例并暴露添加,移除,通知观察这变化的办法。虽然结果也能实现,但是代码复杂,组织混乱。尤其是其实这里并不适用这个模式,让所有的按钮对单击事件感兴趣,如果某个按钮的单击事件会使得多个组件,多个页面,多个所谓的“观察者”感兴趣的话,才比较适合。一个主题通知所有观察者去变化。而这里,组件的单击事件存在互斥关系,且唯有且仅有此optionView
的控件感兴趣。所以还不如用 维护一个变量来得实在。
指示器的变化思路:可以通过改变控件的View.setVisibility(visibility);
方法来显示和隐藏控件。
需求:在单击optionView
的事件中,要将 主页面 替换成 相应的页面。
实现:Fragment 实现。4个optionView
分别对应4个Fragment
的子类。在主界面维护显示的Fragment的变量mMain,单击事件中去判断mMain
的是否为空,空实例化相应的Fragment出来,不为空直接调用FragmentTransaction.replace(R.id.main, mMain, "main")
方法替换掉。需求实现。
值得注意的是:处理好Fragment 与 Activity 之间的生命周期问题。调用replace方法,会调用remove方法,所以要在Fragment的生命周期里恰当的保存好所需的信息。同时要处理好Activity的重建,例如设备转换屏幕时。
需求:第一个optionView
所对应的Fragment需实现一个上部图片轮滑的效果来显示广告,同时需要显示4个系列的入口。整体效果应如下:
实现:上部利用ViewPager 加 自定义的 IndicatorView 来实现圆点效果。中间显示的利用GridView来实现。主要是适配器Mainshow
如何适配的问题。实现GridView的OnItemClickListener
事件实现跳转需求。同时解决Fragment生命周期的问题,避免重复加载布局。该需求最大的难点为适配器。所以说说适配器是如何实现的。适配器的关键是Item的设置。在该需求中,选择自定义控件ListImage
来填充Item。ListImage
为左图,标题,分割线,图片,分别对应TextView,TextView,ImageView的复合型控件,实现起来不难。但是要控制ImageView图片的适配问题。同时解决如何不同分割线的绘制。需求实现。
实现分割线效果:我实现的思路将分割线分割成一个小图片,然后平铺。而Android实现平铺效果的代码如下:
需求实现。
实现图片轮滑效果:这个实现了ViewPager.PageAdapter
,问题解决,指示器的样式只要传给指示器一个ViewPage的实例即可。这指示器基本采用网上源码来实现。所以没什么好说的。传好参数就行了。
解决布局问题:这里我使用了Lin为了但是我们要让其显示在Item的最下面,但是实际效果为
需求:在通过主界面点击ListImage
后进入列表显示商品介绍。每一个Item显示图片,显示手表名称,手表简介,手表获得的赞数,手表获得的评论。单击Item触发单击事件,进入该手表的详情信息页。
在这个需求里,我遇到的问题为:这些信息都是通过网络来的,所以我必须跟服务器进行交互。这里我直接封装了常用的网络访问库 NetHelp
,使用基本的HttpURLConnection
与服务器进行交互,暴露sendRequestWithHttpConnect(String request,int Method, IDate i)
方法,接受请求命令,注意不是完整的url地址,有关于url的地址会在方法内进行拼装,因为我提供了一个合同类Contract
定义了与服务端的数据交互的url常量。在NetHelp
类中我可以维护一个保存当前请求的参数mparmas
的HashMap
类型,如果服务端的请求url发生变化时,不必更改所有使用的url,同时保证拼接url不会出现拼接不完全的问题。因为一个完整的url我分成了三部分,指向服务端的域名的基本url,向服务端请求的具体方法,携带的参数 key - value 值。尤其是在拼接参数时同时对参数进行中文编码。当然,在NetHelp
类中不处理返回的response,由调用者来自行决定如何处理。我提供一个IDate
接口,里面只有一个方法HandlerDate(HttpURLConnection c)
,可以让调用者回调利用返回的HttpURLConnection
自行处理返回的结果。
回到这个需求,首次进入我们肯定没有数据,所以我们要向服务端请求数据,服务端返回的一大段json,正常来讲为服务器返回的为 该数据集合的实体类的list集合的json。这里就产生了两个问题:1、要不要使用实体类封装数据。2、数据量庞大时,内存不足怎么解决。我自己思考的答案是:要用实体类,封装简单,在adapter适配器里适配数据时也更容易,数据量庞大时,使用化整体为部分的思想,要么向服务器请求部分数据,类似网页的分页。要么将数据存储在本地进行处理。在这个请求里,往极限了想,如果服务端返回10000条数据需要我们显示,存储在本地没必要,我们显示的也就那么点,所以第一让服务器返回显示的数据总数,我们根据数据总数,将数据分割成单次显示的数量,或者传给服务器两个参数,页数page,每页显示的数量pagesize,让服务器自己返回数据集合,我们再添加一个“加载更多”的控件,让page变化就可以了。解析json,网上一大堆成熟的框架,也是分分钟的事。剩下的就是发起请求的问题。值得注意的是,Android并不允许我们在UI线程中去访问网络,所以使用异步的 AsyncTask
就是板上钉钉的事了。传好参数,数据成功通过回调函数获得。
解决了获取数据的问题,接下来就是如何填充数据的问题了,即ListView
的ListAdapter
如何写。首先,在 字符串类型的填充很容易,但麻烦的是填充图片。我们需要解决图片下载,缓存,oom问题,自适应问题,乱序问题,滑动闪烁问题。问题一个一个来。
ListAdapter
最重要的方法getView()
重写过程中,很容易获得传过来的数据源的实体类对象。值得注意的是,在数据源的json中,图片并没有在实体类中完全解析成相应的Bitmap
对象或drawable
对象,当然如果项目需求需要,我们也可以实现,但是对于不知道什么时候使用实体类,让实体类去保存维护一堆bitmap对象,这个代码就不是那么优雅了。我的做法是保存的是路径或图片的名称,使用时根据其路径或名称再通过网络或本地文件或数据库解析成Bitmap
对象。在加载的Adapter的布局文件中,控件的实例能找到并通过实体类进行封装,但是到ImageView
这里就有点特殊了。因为填充网络图片就会遇到上面所说的问题。
首先,解决乱序问题。为ImageView.setTag(url)
设置独有的Tag属性。在Adapter里维护一个当前适配的ListView引用,每次填充图片时,调用ListView.findViewWithTag(TagName)
找到填充指定的ImageView
.问题解决。
缓存问题。缓存问题是为了辅助oom问题引入的。这个需求采用LruCache
来管理图片缓存。最大缓存设为最大可用内存的1/8
自适应问题。在这个需求中,要求图片的宽刚好能放满整个手机设备的屏幕宽,高度自定。而我们向服务器请求的图片url可能宽度无法填满或溢出,所以我们要根据屏幕的宽进行缩放。为此定义了HelpUntil.zoom(Bitmap mBitmap, int width, int height)
将指定的Bitmap按指定的寛高进行缩放。问题解决。这里其实也可以把这个过程放到服务端处理,给服务端发送所需图片的寛高,让服务端为我们处理好,也可以。
滑动闪烁问题:滑动闪烁的问题是出在了ListView的滑动事件里。当ListView里滑动时,后台图片仍然在下载,导致各种跳跃,闪烁。所以在滑动时我们应该控制其ListView的滑动事件,当滑动时,停止下载,仅去下载当前可见范围的ImageView.即下载图片的调用放置在ListView的滑动事件上。所以在Adpater里暴露了cancelAllTasks()
取消所有下载任务的方法与读取可见图片源的方法loadBitmaps(int firstVisibleItem, int visibleItemCount)
。问题解决。
图片获取:图片获取的地方有两个:一个为本地缓存,即LruCache
,另一个为网络请求了。总是先在缓存查询是否有已经下载好的图,有则显示,没有再去下载。这里的下载是典型的多线程下载。故使用了异步类BitmapWorkerTask
继承AsyncTask
解决单个图片下载问题。private Set
维护所有的下载任务。重要的是BitmapWorkerTask
里如何下载。很好,这里就运用到了之前封装的NetHelp的方法了。在BitmapWorkerTask
内部中调用复写doInBackground
,封装好请求,BitmapWorkerTask
实现IDate接口,将自身传给NetHelp的sendRequestWithHttpConnect(String request,int Method, IDate i)
,doInBackground
就完成了。无论是对将数据流解析成Bitmap对象,还是将Bitmap放入缓存,缩放等等都在HandlerDate()
方法里解决了。当然处理完下载图片的问题,还要在UI线程更新数据。那么就在onPostExecute(Bitmap bitmap)
通过Tag找到相应的ImageView控件,将下载好的图片显示出来。在taskCollection移除任务就可以了。到这里,需求就完整实现了。
最后一个问题oom问题:避免加载大对象,不要将高清大图直接加载到内存中去,只保存图片尺寸大小,不保存图片到内存。同时记得调用Bitmap 的recycle()
方法通知清理图片资源。
另外一种思路:使用Volley框架的 ImageLoader
和 NetworkImageView
也能实现需求。
@null
setCompoundDrawablesWithIntrinsicBounds
的应用