Android仿微信实现快速索引选择联系人

一.概述

先看效果图,然后在给大家慢慢介绍
这里写图片描述

二.实现

先给大家说说这些城市的数据是怎么来的,在实际开发中,一般都是从服务器里获取过来的,这里因为不太方便,我就没有使用服务器,而是直接把数据保存在了本地文件,模拟访问服务器然后去读取数据,过程不重要,我们这里重点是获得数据,我们先看看这些数据放在哪吧。
这里写图片描述
在当前项目的assets目录下,我们放入了json格式的数据,我们打开看一下

{
    "state": 1,
    "datas": [
        {
            "id": "820",
            "name": "安阳",
            "sortKey": "A"
        },
        {
            "id": "68",
            "name": "安庆",
            "sortKey": "A"
        },
        {
            "id": "1269",
            "name": "鞍山",
            "sortKey": "A"
        },
        {
            "id": "22",
            "name": "蚌埠",
            "sortKey": "B"
        },
        {
            "id": "1372",
            "name": "包头",
            "sortKey": "B"
        },
        {
            "id": "2419",
            "name": "北京",
            "sortKey": "B"
        },
        {
            "id": "649",
            "name": "保定",
            "sortKey": "B"
        },
        {
            "id": "1492",
            "name": "宝鸡",
            "sortKey": "B"
        },

由于数据比较多,我们只列举了一部分,其他格式完全相同,我们就把这个当做从模拟器获取到的数据,然后进行操作。我们如何操作呢,这里写了一个类

public class AppJsonFileReader {
    /**
     * 获取json文件中的数据
     * @param context
     * @param fileName
     * @return json字符串数据
     */
    public static String getJson(Context context,String fileName){
        StringBuilder builder = new StringBuilder();
        AssetManager manager = context.getAssets();
        try {
            InputStream stream = manager.open(fileName);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream));
            String line = null;
            while((line = bufferedReader.readLine())!=null){
                builder.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return builder.toString();
    }

    /**
     * 从json字符串数据中获取对象集合
     * @param str
     * @return 对象集合
     */
    public static List<City> setData(String str){
        List<City> list = new ArrayList<>();
        City city ;
        try {
            JSONObject result = new JSONObject(str);
            JSONArray array = result.getJSONArray("datas");
            int len = array.length();
            for (int i = 0; i <len ; i++) {
                JSONObject object = array.getJSONObject(i);
                city = new City();
                city.setId(object.getString("id"));
                city.setName(object.getString("name"));
                city.setSortKey(object.getString("sortKey"));
                list.add(city);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return list;
    }
}

接下来我们先看布局文件:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="example.lxn.com.app.view.CityIndex">
    <RelativeLayout
        android:id="@+id/rlTop"
        android:layout_width="match_parent"
        android:padding="10dp"
        android:background="#FF6633"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#FFFFFF"
            android:text="选择城市"
            android:textSize="20sp"
            android:layout_centerInParent="true"
            />
    </RelativeLayout>
    <ListView
        android:id="@+id/cityList"
        android:layout_below="@+id/rlTop"
        android:scrollbars="none"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"></ListView>
    <example.lxn.com.app.view.SliderBar
        android:id="@+id/sliderBar"
        android:layout_below="@+id/rlTop"
        android:layout_alignParentRight="true"
        android:layout_width="40dp"
        android:layout_height="match_parent" />
    <TextView
        android:id="@+id/tv_center"
        android:layout_width="100dp"
        android:gravity="center"
        android:textSize="60sp"
        android:visibility="invisible"
        android:textColor="#ffffff"
        android:layout_height="100dp"
        android:layout_centerInParent="true"
        android:background="@drawable/text_index"
        />
</RelativeLayout>

我们的重点在这个SliderBar上面,这是一个自定义的View,接下来看如何实现:
我们先来画字母,这里的难点是确定每个字母的位置,我们先来看一张图
这里写图片描述
Android中绘制文字时,默认的起始位置是当前坐标的左下角,也就是图中字母A的左边第一个点,这个位置我们可以设置,一共有三个位置

         paint.setTextAlign(Paint.Align.CENTER);//文字中间
        paint.setTextAlign(Paint.Align.LEFT);//文字左边
        paint.setTextAlign(Paint.Align.RIGHT);//文字右边

在这里我们会将起始点左边设为中间位置,也就是图中的第二个红点,便于计算。

那么第二个红点的坐标如何确定呢,我们先看x坐标,因为我们的字母是绘制在view之中的,我们很容易看出当前其实的x坐标为当前view宽度的一半。
然后我们看y坐标,这个稍微有点复杂,假设图中的每个黑色格子代表文字的绘制区域,大家看那条蓝色的线,就是格子的平分线,那么第一个字母的y坐标就等于格子的一半加上文字高度的一半,格子的一半我们可以计算出来,因为每个格子的高度等于view的总高度除以字母的个数,然后我们就可以得到每个格子的高度,然后除以2。

接下来我们看如何得到每个文字高度的一半,我们通过下面这个函数来获取

public void getTextBounds(String text, int start, int end, Rect bounds)

这个函数的作用是得到文字所在矩形的宽和高,也就是图中字母C周围的那个蓝色的矩形,这个函数有四个参数,第一个参数为要获得边界的文字,第二个参数为文字长度的起始位置,第三个参数为文字长度的结束位置,最后一个参数为一个Rect对象,当我们调用这个函数以后就把当前文字所在矩形的宽和高存到了第四个参数Rect中。

经过上面的讲解,我们就应该知道如何获得当前文字所要绘制的起点y坐标了吧。下面给出代码:

public class SliderBar extends View {
    private String[] siderBar = { "热门", "A", "B", "C", "D", "E", "F", "G", "H", "J",
            "K", "L", "M", "N", "Q", "S", "T", "W", "X", "Y", "Z" ,"#"};
    private Paint paint = new Paint();//创建画笔对象
    private Context context;
    public SliderBar(Context context) {
        super(context);
        this.context = context;
    }
    public SliderBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        paint.setColor(Color.BLACK);//设置画笔颜色为黑色
        paint.setTypeface(Typeface.DEFAULT_BOLD);//默认字体为粗体
        paint.setAntiAlias(true);//反锯齿
        paint.setTextSize(DisplayUtil.px2sp(context, 15));//设置姚绘制的文字大小
        paint.setTextAlign(Paint.Align.CENTER);//设置绘制的起点为中心
        int height = getHeight();//当前view的高度,用于计算每个文字占用的高度
        int width = getWidth();//当前viewde宽度
        int eachHeight = height/siderBar.length;//每个字母的高度 = 总高度/文字个数
        for (int i = 0 ; i < siderBar.length; i++) {
            Rect bounds = new Rect();
            paint.getTextBounds(siderBar[i],0,siderBar[i].length(),bounds);
            float x = width/2;
            float y = eachHeight/2+ bounds.height()/2+ i*eachHeight;
            canvas.drawText(siderBar[i],x,y,paint);//根据x,y坐标画出当前文字
        }
    }

这时候我们看一下效果图
这里写图片描述
此时已经正确画出了字母,

接下来我们为ListView填充数据,也就是显示出左边的城市信息。我们先写出adapter,这里的难点事如何显示如一个字母以及下面的城市信息,我们这里的思路是把索引和当前城市的信息看做一个item,然后代码里面动态去判断是否要显示当前城市的索引。

public class CityAdapter extends BaseAdapter {
    private Context context;
    private List<City> list;
    //存放索引的信息
    StringBuilder builder = new StringBuilder();
    //存放城市的名称
    private List<String> nameList = new ArrayList<>();
    public CityAdapter(Context context,List<City> list){
        this.context = context;
        this.list = list;
    }
    @Override
    public int getCount() {
        return list.size();
    }
    @Override
    public City getItem(int position) {
        return list.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if(convertView == null){
            holder = new ViewHolder();
            convertView = View.inflate(context,R.layout.city_list_item_view,null);
           holder.index = (TextView) convertView.findViewById(R.id.tv_index);
            holder.name = (TextView) convertView.findViewById(R.id.tv_city);
            convertView.setTag(holder);
        }else{
            holder = (ViewHolder) convertView.getTag();
        }
        City city = list.get(position);
        String sortKey=  city.getSortKey();
        String cityName = city.getName();
        //当前索引不存在,添加索引和城市名称
        if(builder.indexOf(sortKey)==-1){
            builder.append(sortKey);
            nameList.add(cityName);
        }
        //城市名称存在,显示索引
        if(nameList.contains(cityName)){
            holder.index.setVisibility(View.VISIBLE);
            holder.index.setText(sortKey);
        }else{
            holder.index.setVisibility(View.GONE);
        }
        holder.name.setText(cityName);
        return convertView;
    }
    public class ViewHolder{
        public TextView name;
        public TextView index;
    }
}

接着我们来获取数据,显示数据

public class MainActivity extends ActionBarActivity {
    private ListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.cityList);
       //执行获取数据的任务
        new GetJsonTask().execute();
    }
    public class GetJsonTask extends AsyncTask<Void,Void,String>{
        @Override
        protected String doInBackground(Void... params) {
        //读取json数据
            String result = AppJsonFileReader.getJson(MainActivity.this,"city.json");
            return result;
        }
        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            //处理数据
            listView.setAdapter(new CityAdapter(MainActivity.this,AppJsonFileReader.setData(s)));
        }
    }
}

然后我们看效果图
这里写图片描述

接下来我们写最后的一步,为字母所在的view添加触摸事件并且控制定位左边联系人,这里我们使用回调,直接上代码

public class SliderBar extends View {
    private String[] siderBar = { "热门", "A", "B", "C", "D", "E", "F", "G", "H", "J",
            "K", "L", "M", "N", "Q", "S", "T", "W", "X", "Y", "Z" ,"#"};
    Paint paint = new Paint();
    private Context context;
    private OnTouchLetterChangedListener listener;
    public interface OnTouchLetterChangedListener{
         void onTouchLetterChanged(String s);
    }
    public void setOnTouchLetterChangedListener(OnTouchLetterChangedListener listener){
        this.listener = listener;
    }
    public SliderBar(Context context) {
        super(context);
        this.context = context;
    }
    public SliderBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setAntiAlias(true);
        paint.setTypeface(Typeface.DEFAULT_BOLD);
        paint.setColor(Color.BLACK);
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTextSize(15);
        int height = getHeight();
        int width = getWidth();
        int eachHeight = height/siderBar.length;
        for(int i = 0;i<siderBar.length;i++){
            Rect rect = new Rect();
            paint.getTextBounds(siderBar[i],0,siderBar[i].length(),rect);
            float x = width/2;
            float y = eachHeight/2+rect.height()/2+i*eachHeight;
            canvas.drawText(siderBar[i],x,y,paint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        float y = event.getY();
        int index = (int) (y/getHeight()*siderBar.length);
        switch (action){
            case MotionEvent.ACTION_UP:
                setBackgroundResource(android.R.color.transparent);
                invalidate();
                break;
          default:
                setBackgroundResource(R.drawable.text_index);
                if(index>0&&index<siderBar.length){
                    if(listener!=null){
                        listener.onTouchLetterChanged(siderBar[index]);
                        invalidate();
                    }
                }
                break;
        }
        //注意,这里必须要返回true,目的就是不让listview响应触摸事件,因为我们要通过滑动控制listview的显示
        return true;
    }
}
public class MainActivity extends Activity implements SliderBar.OnTouchLetterChangedListener {
    private ListView listView;
    private List<City> list;
    private TextView tvCenter;
    private SliderBar sliderBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.cityList);
        tvCenter = (TextView) findViewById(R.id.tv_center);
        sliderBar = (SliderBar) findViewById(R.id.sliderBar);
        sliderBar.setOnTouchLetterChangedListener(this);
        new GetJsonTask().execute();
    }
    @Override
    public void onTouchLetterChanged(String s) {
        int index = findIndex(list, s);
        if(index!=-1) {
            listView.setSelection(index);
            showText(s);
        }
    }

    /**
     * 查找选中字母在集合中的位置
     * @param list
     * @param s
     * @return
     */
    public int findIndex(List<City> list,String s){
        for(int i = 0;i<list.size();i++){
            City city = list.get(i);
            if(city.getSortKey().equals(s)){
                return i;
            }
        }
        return -1;
    }
    /**
     * 在屏幕中央显示选中的文字
     * @param text
     */
    public void showText(String text){
        tvCenter.setVisibility(View.VISIBLE);
        tvCenter.setText(text);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                tvCenter.setVisibility(View.GONE);
            }
        },200);
    }
    public class GetJsonTask extends AsyncTask<Void,Void,String>{
        @Override
        protected String doInBackground(Void... params) {
            String result = AppJsonFileReader.getJson(MainActivity.this,"city.json");
            return result;
        }
        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            list = AppJsonFileReader.setData(s);
            listView.setAdapter(new CityAdapter(MainActivity.this,list));
        }
    }
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值