自定义View学习之12/4(仿IOS联系人列表)

今天我们来仿一个IOS联系人列表,首先得支持字母行置顶(有阴影和没阴影置顶)。支持右边字母视图点击和滑动到置顶的字母行。搜索栏支持中英文搜索。有了这个需求,我们现在就得开始动手做。

动手前我们得理清思路:
1、需要重写一个有置顶功能的列表控件;
2、需要写一个右边字母控件竖排视图;
3、支持中英文就得把中文转成拼音,这里我用了google系统用的HanziToPinyin这个类来做。
4、然后我们得有一个全部联系人的集合。一个显示UI联系人的集合和一个每个联系人的首字母第一次出现的Map集合来保存下标;
5、先贴下动态图,马上开始动手做;

这里写图片描述

首先我们先写右边的字母视图代码如下(由于个人习惯写代码一般喜欢加上注释,这里不加以详细描述):

package com.example.phonedemo;

import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

public class LetterIndexView extends LinearLayout {

    /**
     * 上下文环境
     */
    private Context context;
    /**
     * 字母控件
     */
    private TextView[] lettersTxt = new TextView[28];
    /**
     * 触碰字母索引接口
     */
    private OnTouchLetterIndex touchLetterIndex;

    public LetterIndexView(Context context) {
        super(context);
        this.context = context;
    }

    public LetterIndexView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    /**
     * 初始化控件.
     */
    public void init(OnTouchLetterIndex touchLetterIndex) {
        this.touchLetterIndex = touchLetterIndex;
        this.setBackgroundColor(getResources().getColor(R.color.transparent));
        this.setOrientation(LinearLayout.VERTICAL);
        this.setGravity(Gravity.CENTER);
        //创建字母控件实例
        for (int i = 0; i < 28; i++) {
            lettersTxt[i] = new TextView(context);
            lettersTxt[i].setGravity(Gravity.CENTER);
            char tab = (char) (i + 63);
            if (i == 0)
                lettersTxt[i].setText("!");
            else if (i == 1)
                lettersTxt[i].setText("#");
            else
                lettersTxt[i].setText("" + tab);
            lettersTxt[i].setPadding(10, 0,10, 0);
            lettersTxt[i].setBackgroundColor(0xFF0000);
            lettersTxt[i].setTextSize(12);
            lettersTxt[i].setTextColor(Color.BLACK);
            LayoutParams letterParam = new LayoutParams(LayoutParams.WRAP_CONTENT, 0);
            letterParam.weight = 1;
            lettersTxt[i].setLayoutParams(letterParam);
            this.addView(lettersTxt[i]);
        }

        this.setOnTouchListener(new OnTouchListener() {
            //移动y轴的距离
            private int y;
            //控件的高度
            private int height;
            //按到了哪个字母
            private String tab;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        //按下时改变背景和字体颜色
                        setTextColor(Color.WHITE);
                        LetterIndexView.this.setBackgroundColor(getResources().getColor(R.color.ff7c7c7c));
                    case MotionEvent.ACTION_MOVE:
                        // 获取触发事件点的纵坐标
                        y = (int) event.getY(); 
                        height = LetterIndexView.this.getHeight();
                        int location = (int) (y / (height / 28) + 0.5f);
                        if (location == 0) {
                            tab = "!";
                        } else if (location == 1) {
                            tab = "#";
                        } else if (location > 0 && location <= 27) {
                            tab = String.valueOf((char) (location + 63));
                        }
                        if (LetterIndexView.this.touchLetterIndex!=null) {
                            //调用接口
                            LetterIndexView.this.touchLetterIndex.touchLetterWitch(tab);
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        LetterIndexView.this.setBackgroundColor(getResources().getColor(R.color.transparent));
                        if (LetterIndexView.this.touchLetterIndex!=null) {
                            //调用接口
                            LetterIndexView.this.touchLetterIndex.touchFinish();
                        }
                        setTextColor(Color.BLACK);
                        break;
                }
                return true;
            }
        });
    }

    /**
     * 设置字体颜色
     */
    private void setTextColor(int color) {
        for (int i = 0; i < 28; i++) {
            lettersTxt[i].setTextColor(color);
        }
    }

    /**
     * 触碰字母索引接口
     */
    public interface OnTouchLetterIndex {

        /**
         * 触摸字母空间接口.
         */
        void touchLetterWitch(String letter);

        /**
         * 结束查询
         */
        void touchFinish();

    }
}

由于时间原因自己也没研究置顶功能的listview,于是我拿的网上开源控件这里就不贴出来了。主界面代码如下(由于个人习惯写代码一般喜欢加上注释,这里不加以详细描述):

package com.example.phonedemo;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;

public class MainActivity extends Activity {
    /**
     * 搜索栏
     */
    EditText edit_search;
    /**
     * 列表
     */
    PinnedSectionListView listView;
    /**
     * 右边字母列表
     */
    LetterIndexView letterIndexView;
    /**
     * 中间显示右边按的字母
     */
    TextView txt_center;

    /**
     * 所有名字集合
     */
    private ArrayList<PhoneBean> list_all;
    /**
     * 显示名字集合
     */
    private ArrayList<PhoneBean> list_show;
    /**
     * 列表适配器
     */
    private PhoneAdapter adapter;
    /**
     * 保存名字首字母
     */
    public HashMap<String, Integer> map_IsHead;
    /**
     * item标识为0
     */
    public static final int ITEM = 0;
    /**
     * item标题标识为1
     */
    public static final int TITLE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        edit_search = (EditText) findViewById(R.id.edit_search);
        listView = (PinnedSectionListView) findViewById(R.id.phone_listview);
        letterIndexView = (LetterIndexView) findViewById(R.id.phone_LetterIndexView);
        txt_center = (TextView) findViewById(R.id.phone_txt_center);

        initView();
        initData();
    }

    private void initView() {

        // 输入监听
        edit_search.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i,
                    int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1,
                    int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                list_show.clear();
                map_IsHead.clear();
                //把输入的字符改成大写
                String search = editable.toString().trim().toUpperCase();

                if (TextUtils.isEmpty(search)) {
                    for (int i = 0; i < list_all.size(); i++) {
                        PhoneBean bean = list_all.get(i);
                        //中文字符匹配首字母和英文字符匹配首字母
                            if (!map_IsHead.containsKey(bean.getHeadChar())) {// 如果不包含就添加一个标题
                                PhoneBean bean1 = new PhoneBean();
                                // 设置名字
                                bean1.setName(bean.getName());
                                // 设置标题type
                                bean1.setType(MainActivity.TITLE);
                                list_show.add(bean1);
                                // map的值为标题的下标
                                map_IsHead.put(bean1.getHeadChar(),
                                        list_show.size() - 1);
                            }
                            // 设置Item type
                            bean.setType(MainActivity.ITEM);
                            list_show.add(bean);
                    }
                } else {
                    for (int i = 0; i < list_all.size(); i++) {
                        PhoneBean bean = list_all.get(i);
                        //中文字符匹配首字母和英文字符匹配首字母
                        if (bean.getName().indexOf(search) != -1|| bean.getName_en().indexOf(search) != -1) {
                            if (!map_IsHead.containsKey(bean.getHeadChar())) {// 如果不包含就添加一个标题
                                PhoneBean bean1 = new PhoneBean();
                                // 设置名字
                                bean1.setName(bean.getName());
                                // 设置标题type
                                bean1.setType(MainActivity.TITLE);
                                list_show.add(bean1);
                                // map的值为标题的下标
                                map_IsHead.put(bean1.getHeadChar(),
                                        list_show.size() - 1);
                            }
                            // 设置Item type
                            bean.setType(MainActivity.ITEM);
                            list_show.add(bean);
                        }
                    }
                }

                adapter.notifyDataSetChanged();

            }
        });

        // 右边字母竖排的初始化以及监听
        letterIndexView.init(new LetterIndexView.OnTouchLetterIndex() {
            //实现移动接口
            @Override
            public void touchLetterWitch(String letter) {
                // 中间显示的首字母
                txt_center.setVisibility(View.VISIBLE);
                txt_center.setText(letter);
                // 首字母是否被包含
                if (adapter.map_IsHead.containsKey(letter)) {
                    // 设置首字母的位置
                    listView.setSelection(adapter.map_IsHead.get(letter));
                }
            }
            //实现抬起接口
            @Override
            public void touchFinish() {
                txt_center.setVisibility(View.GONE);
            }
        });

        /** listview点击事件 */
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view,int i, long l) {
                if (list_show.get(i).getType() == MainActivity.ITEM) {// 标题点击不给操作
                    Toast.makeText(MainActivity.this,list_show.get(i).getName(), Toast.LENGTH_LONG).show();
                }
            }
        });

        // 设置标题部分有阴影
        // listView.setShadowVisible(true);
    }

    protected void initData() {
        list_all = new ArrayList<PhoneBean>();
        list_show = new ArrayList<PhoneBean>();
        map_IsHead = new HashMap<String, Integer>();
        adapter = new PhoneAdapter(MainActivity.this, list_show, map_IsHead);
        listView.setAdapter(adapter);

        // 开启异步加载数据
        new Thread(runnable).start();
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            adapter.notifyDataSetChanged();
        }
    };

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            String[] str = getResources().getStringArray(R.array.phone_all);

            for (int i = 0; i < str.length; i++) {
                PhoneBean cityBean = new PhoneBean();
                cityBean.setName(str[i]);
                list_all.add(cityBean);
            }
            //按拼音排序
            MemberSortUtil sortUtil = new MemberSortUtil();
            Collections.sort(list_all, sortUtil);

            // 初始化数据,顺便放入把标题放入map集合
            for (int i = 0; i < list_all.size(); i++) {
                PhoneBean cityBean = list_all.get(i);
                if (!map_IsHead.containsKey(cityBean.getHeadChar())) {// 如果不包含就添加一个标题
                    PhoneBean cityBean1 = new PhoneBean();
                    // 设置名字
                    cityBean1.setName(cityBean.getName());
                    // 设置标题type
                    cityBean1.setType(MainActivity.TITLE);
                    list_show.add(cityBean1);

                    // map的值为标题的下标
                    map_IsHead.put(cityBean1.getHeadChar(),list_show.size() - 1);
                }
                list_show.add(cityBean);
            }

            handler.sendMessage(handler.obtainMessage());
        }
    };

    public class MemberSortUtil implements Comparator<PhoneBean> {
        /**
         * 按拼音排序
         */
        @Override
        public int compare(PhoneBean lhs, PhoneBean rhs) {
            Comparator<Object> cmp = Collator
                    .getInstance(java.util.Locale.CHINA);
            return cmp.compare(lhs.getName_en(), rhs.getName_en());
        }
    }

}

然而适配器则是用的getViewTypeCount()这种方式,缓存2个不同的view来实现,个人认为是已经不能再优化的方案了。如有更好方案欢迎提出来,我加以改正,代码如下(由于个人习惯写代码一般喜欢加上注释,这里不加以详细描述):

package com.example.phonedemo;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import com.example.phonedemo.PinnedSectionListView.PinnedSectionListAdapter;


public class PhoneAdapter extends BaseAdapter implements PinnedSectionListAdapter {
    private LayoutInflater layoutInflater;
    /**
     * 数据集
     */
    private ArrayList<PhoneBean> list;
    /**
     * 首字母
     */
    public HashMap<String, Integer> map_IsHead;

    public PhoneAdapter(Context context, ArrayList<PhoneBean> list, HashMap<String, Integer> map_IsHead) {
        this.list = list;
        this.map_IsHead = map_IsHead;
        layoutInflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Object getItem(int i) {
        return list.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public int getItemViewType(int position) {
        return list.get(position).getType();
    }

    //实现自定义listview的接口
    @Override
    public boolean isItemViewTypePinned(int viewType) {
        return viewType == MainActivity.TITLE;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        final ViewHolder viewHolder;
        switch (getItemViewType(i)) {
            case MainActivity.ITEM:
                if (view == null) {
                    viewHolder = new ViewHolder();
                    view = layoutInflater.inflate(R.layout.item_phone_item, null);
                    viewHolder.txt = (TextView) view.findViewById(R.id.item_phone_txt_name);
                    view.setTag(viewHolder);
                } else {
                    viewHolder = (ViewHolder) view.getTag();
                }

                //设置名字
                viewHolder.txt.setText(list.get(i).getName());
                break;
            case MainActivity.TITLE:
                if (view == null) {
                    viewHolder = new ViewHolder();
                    view = layoutInflater.inflate(R.layout.item_phone_title, null);
                    viewHolder.txt = (TextView) view.findViewById(R.id.item_phone_txt_head);
                    view.setTag(viewHolder);
                } else {
                    viewHolder = (ViewHolder) view.getTag();
                }
                //设置标题
                viewHolder.txt.setText(list.get(i).getHeadChar());
                break;
        }

        return view;
    }


    private class ViewHolder {
        private TextView txt;
    }

}

而中文切成拼音,我则是直接放到bean里面来做,方便点代码如下(由于个人习惯写代码一般喜欢加上注释,这里不加以详细描述):

package com.example.phonedemo;


import java.util.ArrayList;

import android.text.TextUtils;

public class PhoneBean {
    /**
     * 城市名字首字母
     */
    private String headChar;
    /**
     * 城市id
     */
    private String city_id;
    /**
     * 省id
     */
    private String pro_id;
    /**
     * 城市名字
     */
    private String name;
    /**
     * 城市字母名字
     */
    private String name_en;
    /**
     * 是否是标题
     */
    private int type;

    public String getHeadChar() {
        return headChar;
    }

    public String getName() {
        return name;
    }

    public String getName_en() {
        return name_en;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public void setName(String name) {
        this.name = name;
        name_en = getPinYin(name);//获取字母名称
        name_en = name_en.toUpperCase();//把小写字母换成大写字母
        if (!TextUtils.isEmpty(name_en)) {
            char head = name_en.charAt(0);
            if (head < 'A' || head > 'Z') {
                head = '#';
            }
            headChar = head + "";
        }
    }

    /**
     * 汉字转换拼音,字母原样返回,都转换为小写
     */
    public String getPinYin(String input) {
        ArrayList<HanziToPinyin.Token> tokens = HanziToPinyin.getInstance().get(input);
        StringBuilder sb = new StringBuilder();
        if (tokens != null && tokens.size() > 0) {
            for (HanziToPinyin.Token token : tokens) {
                if (token.type == HanziToPinyin.Token.PINYIN) {
                    sb.append(token.target);
                } else {
                    sb.append(token.source);
                }
            }
        }
        return sb.toString().toLowerCase();
    }

    public String getCity_id() {
        return city_id;
    }

    public void setCity_id(String city_id) {
        this.city_id = city_id;
    }

    public String getPro_id() {
        return pro_id;
    }

    public void setPro_id(String pro_id) {
        this.pro_id = pro_id;
    }
}

最后贴下xml界面布局如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <EditText
        android:id="@+id/edit_search"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:drawableLeft="@drawable/edit_search"
        android:drawablePadding="10dp"
        android:hint="请输入名字或者拼音"
        android:padding="15dp"
        android:singleLine="true"
        android:textColor="@android:color/black"
        android:textColorHint="#ff333333"
        android:textSize="16sp" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/edit_search"
        android:background="@android:color/white" >

        <com.example.phonedemo.PinnedSectionListView
            android:id="@+id/phone_listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:divider="@null" />

        <com.example.phonedemo.LetterIndexView
            android:id="@+id/phone_LetterIndexView"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true" />
    </RelativeLayout>

    <TextView
        android:id="@+id/phone_txt_center"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_centerInParent="true"
        android:background="#30000000"
        android:gravity="center"
        android:textColor="#ff7c7c7c"
        android:visibility="gone" />

</RelativeLayout>

好了,只有部分代码没有贴出来,我觉得是无关紧要的,所以就没贴。其他的基本上都贴出来了。我写代码的习惯就是边写代码边注释,所以在博客上就不浪费时间做重复的事情了。望见谅;

希望大家多多关注我的博客,多多支持我。
如有好意见或更好的方式欢迎留言谈论。

尊重原创转载请注明:(http://blog.csdn.net/u013895206) !

下面是地址传送门:
http://download.csdn.net/detail/u013895206/9273575

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值