概述

最近写论文之余玩起了github,发现有个citypicker挺不错的,高仿了美团城市选择和定位的一些功能

地址链接 效果图如下:



自己手动写了一遍优化了一些内容,学到了一些姿势,下面对其中一些技术点做下总结。

  • 清晰的结构
  • SideLetterBar实现城市列表
  • 如何显示字母浮窗
  • 复杂的Adapter
  • ScrollView中嵌入ListView,GridView冲突的解决

清晰的结构

一般看到一个项目之前我会先看下他的结构规划,学习一下高手们架构上的意识,下面是目录结构

从这里清晰的看出MVC的模型,将工具类util、自定义view都进行了规整划分,这里由于只有一个Activity因此没有用单独的package,一般在项目大的时候还要单独划分,还有对应的网络模块

SideLetterBar.java

这个是仿通信录的的自定义view,主要涉及到了 拦截事件的分发处理、ondraw()方法、经典的回调机制

(关于回调可以参考android中的回调机制

非常非常适合新手学习事件处理时的练习材料!

  • 拦截事件的分发机制

当用于点击到这个区域时,事件分发时需要拦截该ActionDown事件,处理流程就是:点击那里就会出现一个字母的overlay,ActionMove时字母随之改变,ActionUp时字母的overlay就会消失,处理完return true表示该事件被本view消耗了,事件结束。

 @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        final float y = event.getY();
        final int oldChoose = choose;
        final OnLetterChangedListener listener = onLetterChangedListener;
        final int c = (int) (y / getHeight() * b.length);

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                showBg = true;
                if (oldChoose != c && listener != null) {
                    if (c >= 0 && c < b.length) {
                        listener.onLetterChanged(b[c]);
                        choose = c;
                        invalidate();
                        if (overlay != null){
                            overlay.setVisibility(VISIBLE);
                            overlay.setText(b[c]);
                        }
                    }
                }

                break;
            case MotionEvent.ACTION_MOVE:
                if (oldChoose != c && listener != null) {
                    if (c >= 0 && c < b.length) {
                        listener.onLetterChanged(b[c]);
                        choose = c;
                        invalidate();
                        if (overlay != null){
                            overlay.setVisibility(VISIBLE);
                            overlay.setText(b[c]);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                showBg = false;
                choose = -1;
                invalidate();
                if (overlay != null){
                    overlay.setVisibility(GONE);
                }
                break;
        }
        return true;
    }

从上面可以清晰看出这里使用event.getAction()来获得点击事件,然后通过switch分别处理,这里要说一下通过点击位置获取准确字符的过程,关键代码及注释

final float y = event.getY();//获取当前像素长度
……
final int c = (int) (y / getHeight() * b.length);//通过当前像素长度/总像素长度*数组长度=当前点击在数组的index
  • onDraw()方法

该方法主要做俩件事,第一件将A-Z的字母绘绘制到控件中,第二件,点击时将控件背景颜色变化(这里选了透明色)

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (showBg) {
            canvas.drawColor(Color.TRANSPARENT);
        }

        int height = getHeight();
        int width = getWidth();
        int singleHeight = height / b.length;
        for (int i = 0; i < b.length; i++) {
            paint.setTextSize(getResources().getDimension(R.dimen.side_letter_bar_letter_size));
            paint.setColor(getResources().getColor(R.color.gray));
            paint.setAntiAlias(true);
            if (i == choose) {
                paint.setColor(getResources().getColor(R.color.gray_deep));
                paint.setFakeBoldText(true);  //加粗
            }
            float xPos = width / 2 - paint.measureText(b[i]) / 2;
            float yPos = singleHeight * i + singleHeight;
            canvas.drawText(b[i], xPos, yPos, paint);
            paint.reset();
        }

    }

这里核心代码是如何找到每个字母的绘制位置,关键代码如下:

 int singleHeight = height / b.length;//找出每个字母在当前手机上占的像素大小
 ……
 float xPos = width / 2 - paint.measureText(b[i]) / 2;//x的起始位置,计算了字符的宽度
 float yPos = singleHeight * i + singleHeight;//for循环从0开始这里要为i+1
 canvas.drawText(b[i], xPos, yPos, paint);//画出字符

但是我在csdn上随便一找,便看到2012年有人转载过这个控件文章,作者直接拿来用了,没做啥改动,这里的代码明显有些地方不够优雅,比如在ondraw中的for循环中每次都要设置画笔大小颜色啥的,变量定义也不明确比如ondraw中的c是什么鬼。。虽然无伤大雅但是看着总有些不舒服,这个控件在我的github中稍微做了一点改写

我的githubSideSelectBar.java

主要将paint画笔初始化位置做了改变、规范了变量,做了一些注释。

如何显示字母浮窗

主要使用overlay覆盖上去,在SideLetterBar 中留了一个函数

    /**
     * 设置悬浮的textview
     * @param overlay
     */
    public void setOverlay(TextView overlay){
        this.overlay = overlay;
    }

然后在activty_city_list.xml布局文件中定义了一个长宽一致固定的textview,android:id=”@+id/tv_letter_overlay”先把属性设置为gone,在CityPickerActivity中获取该空间通过setOverlay传到sideLetterBar中,在ActionDown处理中将该空间设置为visible然后通过settext设置对应的字母

    case MotionEvent.ACTION_DOWN:
                showBg = true;
                if (oldChoose != c && listener != null) {
                    if (c >= 0 && c < b.length) {
                        listener.onLetterChanged(b[c]);
                        choose = c;
                        invalidate();
                        if (overlay != null){
                            overlay.setVisibility(VISIBLE);
                            overlay.setText(b[c]);
                        }
                    }
                }

                break;

复杂的Adapter

在之前的博客中也介绍过这方面的内容

ListView中ConvertView和ViewHolder

一般是继承BaseAdapter然后遵循以下几步:

1.继承BaseAdapter

2.实现getCount(),getItem(),getItemId(),getView()

3.写一个ViewHolder内部类去存储复用的View

4.在getView()中实现数据的设置

这里要谈的是多个布局同时出现在一个listview中,如上图中在一个listview中出现了3个布局!如何做到的?

这时候就需要

重写 getViewTypeCount() – 返回你有多少个不同的布局

重写 getItemViewType(int) – 由position返回view type id

然后在getview中

 int viewType = getItemViewType(position);//获取对应的类型

然后通过switch case 不同的viewType在getView中convertView(即getView的第二个参数代码中为view)使用的不同的布局文件,简要代码如下:

 @Override
    public View getView(final int position, View view, ViewGroup parent) {
        CityViewHolder holder;
        int viewType = getItemViewType(position);
        switch (viewType){
            case 0:     //定位
                view = inflater.inflate(R.layout.view_locate_city, parent, false);
                ViewGroup container = (ViewGroup) view.findViewById(R.id.layout_locate);
                ……
                });
                break;
            case 1:     //热门
                view = inflater.inflate(R.layout.view_hot_city, parent, false);
                WrapHeightGridView gridView = (WrapHeightGridView) view.findViewById(R.id.gridview_hot_city);
               // GridView gridView = (GridView) view.findViewById(R.id.gridview_hot_city);
               ……
                });
                break;
            case 2:     //所有
                if (view == null){
                    view = inflater.inflate(R.layout.item_city_listview, parent, false);
                   ……
                    });
                }
                break;
        }
        return view;
    }

可以看出不同的case中通过inflate使用了不同的布局文件,关于inflate可以参看以前写的一篇博客

LayoutInflater和inflate的用法,有图有真相

ScrollView中嵌入ListView,GridView冲突的解决

可以看到作者在处理处理GridView时候特意对控件的高做了重写,否则gridview显示不全的,代码如下

public class WrapHeightGridView extends GridView {
    public WrapHeightGridView(Context context) {
        this(context, null);
    }

    public WrapHeightGridView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WrapHeightGridView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, heightSpec);
    }
}

这里关键代码

 int heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);//表示测量格式的高最大是无穷大,因此按照实际情况完全显示出来

结束!

第一次尝试markdown感觉还不赖~

参考文章

http://blog.csdn.net/guozh/article/details/7568668 (2012年的仿通信录代码)

http://blog.csdn.net/jdsjlzx/article/details/8273661 (adapter)

http://www.aitinan.com/3885.html (adapter)

通信录列表+复杂Adapter分析的更多相关文章

  1. 管理Android通信录

    Android提供了Contacts应用程序来管理联系人,并且Android系统还为联系人管理提供了ContentProvider,这就同意其他应用程序以ContentResolver来管理联系人数据 ...

  2. 黎活明8天快速掌握android视频教程--22_访问通信录中的联系人和添加联系人

    Android系统中联系人的通讯录的contentProvide是一个单独的apk,显示在界面的contact也是一个独立的apk,联系人apk通过contentProvide访问底层的数据库. 现在 ...

  3. 非智能手机通信录备份并还原至Android智能手机方法

    随着智能手机早已深入普通用户的生活,2-3线城市的用户也逐渐从使用非智能机换成使用智能机.最近便遇见了这样一个转移通讯录的需求.之前使用的手机型号是BBK K201,通信录中绝大部分保存在了手机中,最 ...

  4. (五)backbone - DEMO - 通信录改造之使用requirejs

    DEMO介绍是 DEMO通信录的扩展,使用requirejs模块化整合 大体实现 • model文件 model/contact.js define(function (){ // user cont ...

  5. 今天研究了一下手机通信录管理系统(C语言)

    题目:手机通信录管理系统 一.题目要求 二.需求分析 三.设计步骤/编写代码 四.上机/运行结果 五.总结 一.题目要求 模拟手机通信录管理系统,实现对手机中的通信录进行管理操作.功能要求: (1)查 ...

  6. 优雅的实现多类型列表的Adapter

    1引言 在开发中经常会遇到,一个列表(RecyclerView)中有多种布局类型的情况.前段时间,看到了这篇文章 [译]关于 Android Adapter,你的实现方式可能一直都有问题(http:/ ...

  7. iOS:通信录(完成)(18-01-18更)

    1.读取通信录 1).9.0以前:AddressBook 2).9.0以后:Contacts 2.调用通信录UI(不弄) 1).9.0以前:AddressBookUI 2).9.0以后:Contact ...

  8. arm上的参数列表传递的分析(以android为例)

    1. Linux中可变列表实现的源码分析 查看Linux0.11的内核源代码,对va_list, va_start, va_arg 的实现如下: va_list的实现没有差别,chartypedef ...

  9. Cocos2d-X3.0 刨根问底(五)----- Node类及显示对象列表源码分析

    上一章 我们分析了Cocos2d-x的内存管理,主要解剖了 Ref.PoolManager.AutoreleasePool这三个类,了解了对象是如何自动释放的机制.之前有一个类 Node经常出现在各种 ...

随机推荐

  1. ubuntu mysql表名大小写区分

    近期开发线上操作系统用的ubuntu,数据库用的mysql,突然发现mysql表名大写报错,找一下原因,看了下mysql的配置,果真可以设置,窃喜. 先找到你MySQL的my.cnf配置文件并修改,当 ...

  2. 安卓自定义View实现钟表

    转载请注明出处:http://blog.csdn.net/baiyuliang2013/article/details/45535227 之前实现过html5版的钟表,html5也有一个画板属性Can ...

  3. 关于V4L2中操作比较重要的几个命令以及一般操作流程总结

    最近在做关于摄像头测试程序相关的一些开发,主要是想要实现在摄像头采集视频的过程中,通过按键来实现拍照,然后将拍照得到的数据保存到一个文件中,通过了解V4L2的一些相关操作,原来,拍照后的数据是保存在一 ...

  4. Java基础---Java---基础加强---内省的简单运用、注解的定义与反射调用、 自定义注解及其应用、泛型及泛型的高级应用、泛型集合的综合

    内省的简单运用: JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则. 采用遍历BeanInfo的所有属性方式来查找和 ...

  5. 11 ContextMenu 上下文菜单按钮

    ContextMenu 上下文菜单 在res下的menu里写菜单项 在逻辑代码中 写OnCreateContextMenu() 方法 将菜单项添加到菜单 对菜单项进行监听 onContextItemS ...

  6. Ext JS 6开发实例(四) :调整主视图

    上文把主界面设置好,但是主视图因为界面的微调出现了显示问题,本文将把它调整好了. 打开app/view/main/Main.js,可以看到主视图是派生于标签面板(Ext.tab.Panel)的.在视图 ...

  7. javascript 下拉列表 自动取值 无需value

    <select id="applyType" name="$!{status.expression}" class="inp" onc ...

  8. 任务定义器——SocketProcessor

    将socket扔进线程池前需要定义好任务,要进行哪些逻辑处理由SocketProcessor定义,根据线程池的约定,作为任务必须扩展Runnable.用如下伪代码表示 protected class ...

  9. UNIX环境高级编程——线程与进程区别

    进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性.进程和线程的区别在于: (1)一个程序至少有一个进程,一个进程至少有一个线程. (2)线程的划分尺度小于进 ...

  10. 图文浅析Binder机制

    总述: Binder是Android系统提供的一种IPC机制,Android系统基本就可以看做基于Binder的C/S架构,Binder也是C/S形式出现,它属于驱动但是驱动的一段内存而不是设备,框架 ...