搜档网
当前位置:搜档网 › android自定义View之Android手机通讯录制作

android自定义View之Android手机通讯录制作

android自定义View之Android手机通讯录制作
android自定义View之Android手机通讯录制作

android自定义View之Android手机通讯录制作

我们的手机通讯录一般都有这样的效果,如下图:

OK,这种效果大家都见得多了,基本上所有的Android手机通讯录都有这样的效果。那我们今天就来看看这个效果该怎么实现。

一.概述

1.页面功能分析

整体上来说,左边是一个ListView,右边是一个自定义View,但是左边的ListView 和我们平常使用的ListView还有一点点不同,就是在ListView中我对所有的联系人进行了分组,那么这种效果的实现最常见的就是两种思路:

1.使用ExpandableListView来实现这种分组效果

2.使用普通ListView,在构造Adapter时实现SectionIndexer接口,然后在Adapter 中做相应的处理

这两种方式都不难,都属于普通控件的使用,那么这里我们使用第二种方式来实现,第一种方式的实现方法大家可以自行研究,如果你还不熟悉ExpandableListView的使用,可以参考我的另外两篇博客:

1.使用ExpandableListView实现一个时光轴

2.android开发之ExpandableListView的使用,实现类似QQ好友列表

OK,这是我们左边ListView的实现思路,右边这个东东就是我们今天的主角,这里我通过自定义一个View来实现,View中的A、B......#这些字符我都通过canvas的drawText 方法绘制上去。然后重写onTouchEvent方法来实现事件监听。

2.要实现的效果

要实现的效果如上图所示,但是大家看图片有些地方可能还不太清楚,所以这里我再强调一下:

1.左边的ListView对数据进行分组显示

2.当左边ListView滑动的时候,右边滑动控件中的文字颜色能够跟随左边ListView 的滑动自动变化

3.当手指在右边的滑动控件上滑动时,手指滑动到的地方的文字颜色应当发生变化,同时在整个页面的正中央有一个TextView显示手指目前按下的文字

4.当手指按下右边的滑动控件时,右边的滑动控件背景变为灰色,手指松开后,右边的滑动控件又变为透明色

二.左边ListView分组效果的实现

无论多大的工程,我们都要将之分解为一个个细小的功能块分步来实现,那么这里我们就先来看看左边的ListView的分组的实现,这个效果实现之后,我们再来看看右边的滑动控件该怎么实现。

首先我需要在布局文件中添加一个ListView,这个很简单,和普通的ListView一模一样,我就不贴代码了,另外,针对ListView中的数据集,我需要自建一个实体类,该实体类如下:

/**

* Created by wangsong on 2016/4/24.

*/

public class User {

private intimg;

private String username;

private String pinyin;

private String firstLetter;

public User() {

}

public String getFirstLetter() {

return firstLetter;

}

public void setFirstLetter(String firstLetter) { this.firstLetter = firstLetter;

}

public intgetImg() {

return img;

}

public void setImg(intimg) {

this.img = img;

}

public String getPinyin() {

return pinyin;

public void setPinyin(String pinyin) {

this.pinyin = pinyin;

}

public String getUsername() {

return username;

}

public void setUsername(String username) {

https://www.sodocs.net/doc/548988550.html,ername = username;

}

public User(String firstLetter, intimg, String pinyin, String username) { this.firstLetter = firstLetter;

this.img = img;

this.pinyin = pinyin;

https://www.sodocs.net/doc/548988550.html,ername = username;

}

username 用来存储用户名,img表示用户图像的资源id(这里我没有准备相应的图片,大家有兴趣可以自行添加),pinyin表示用户姓名的拼音,firstLetter表示用户姓名拼音的首字母,OK ,就这么简单的几个属性。至于数据源,我在strings.xml文件中添加了许多数据,这里就不贴出来了,大家可以直接在文末下载源码看。知道了数据源,知道了实体类,我们来看看在MainActivity中怎么样来初始化数据:

private void initData() {

list = new ArrayList<>();

String[] allUserNames = getResources().getStringArray(R.array.arrUsernames);

for (String allUserName :allUserNames) {

User user = new User();

user.setUsername(allUserName);

String convert =

ChineseToPinyinHelper.getInstance().getPinyin(allUserName).toUpperCase();

user.setPinyin(convert);

String substring = convert.substring(0, 1);

if (substring.matches("[A-Z]")) {

user.setFirstLetter(substring);

user.setFirstLetter("#");

}

list.add(user);

}

Collections.sort(list, new Comparator() {

@Override

public intcompare(User lhs, User rhs) {

if (lhs.getFirstLetter().contains("#")) {

return 1;

} else if (rhs.getFirstLetter().contains("#")) { return -1;

}else{

return

lhs.getFirstLetter().compareTo(rhs.getFirstLetter());

}

}

});

}

首先创建一个List集合用来存放所有的数据,然后从strings.xml文件中读取出来所有的数据,遍历数据然后存储到List集合中,在遍历的过程中,我通过ChineseToPinyinHelper 这个工具类来将中文转为拼音,然后截取拼音的第一个字母,如果该字母是A~Z,那么直接设置给 user对象的firstLetter属性,否则user对象的firstLetter属性为一个#,这是由于我的数据源中有一些不是以汉字开头的姓名,而是以其他字符开头的姓名,那么我将这些统一归为#这个分组。

OK,数据源构造好之后,我还需要对List集合进行一个简单的排序,那么这个排序是Java中的操作,我这里就不再赘述。

构造完数据源之后,接着就该是构造ListView的Adapter了,我们来看看这个怎么做,先来看看源码:

/**

* Created by wangsong on 2016/4/24.

*/

public class MyAdapter extends BaseAdapter implements SectionIndexer {

private List list;

private Context context;

private LayoutInflaterinflater;

public MyAdapter(Context context, List list) {

this.context = context;

this.list = list;

inflater = LayoutInflater.from(context); }

@Override

public intgetCount() {

return list.size();

}

@Override

public Object 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) {

convertView = inflater.inflate(https://www.sodocs.net/doc/548988550.html,yout.listview_item, null);

holder = new ViewHolder();

holder.showLetter = (TextView) convertView.findViewById(R.id.show_letter); https://www.sodocs.net/doc/548988550.html,ername = (TextView) convertView.findViewById(https://www.sodocs.net/doc/548988550.html,ername); convertView.setTag(holder);

} else {

holder = (ViewHolder) convertView.getTag();

}

User user = list.get(position);

https://www.sodocs.net/doc/548988550.html,ername.setText(user.getUsername());

//获得当前position是属于哪个分组

intsectionForPosition = getSectionForPosition(position);

//获得该分组第一项的position

intpositionForSection = getPositionForSection(sectionForPosition);

//查看当前position是不是当前item所在分组的第一个item

//如果是,则显示showLetter,否则隐藏

if (position == positionForSection) {

holder.showLetter.setVisibility(View.VISIBLE);

holder.showLetter.setText(user.getFirstLetter());

} else {

holder.showLetter.setVisibility(View.GONE);

}

return convertView;

}

@Override

public Object[] getSections() {

return new Object[0];

}

//传入一个分组值[A....Z],获得该分组的第一项的position

@Override

public intgetPositionForSection(intsectionIndex) {

for (inti = 0; i

if (list.get(i).getFirstLetter().charAt(0) == sectionIndex) {

return i;

}

}

return -1;

}

//传入一个position,获得该position所在的分组

@Override

public intgetSectionForPosition(int position) {

return list.get(position).getFirstLetter().charAt(0);

}

class ViewHolder {

TextView username, showLetter;

}

}

这个Adapter大部分还是和我们之前的Adapter一样的,只不过这里实现了SectionIndexer接口,实现了这个接口,我们就要实现该接口中的三个方法,分别是getSections(),getPositionForSection(),getSectionForPosition()这三个方法,我们这里用到的主要是后面这两个方法,那我来详细说一下:

1.getPositionForSection(intsectionIndex)

这个方法接收一个int类型的参数,该参数实际上就是指我们的分组,我们在这里传入分组的值【A.....Z】,然后我们在方法中通过自己的计算,返回该分组中第一个item的position。

2.getSectionForPosition(int position)

这个方法接收一个int类型的参数,该参数实际上就是我们的ListView即将要显示的item的position,我们通过传入这个position,可以获得该position的item所属的分组,然后再将这个分组的值返回。

说了这么多,大家可能有疑问了,我为什么要实现这个接口呢?大家来看看我的item 的布局文件:

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:id="@+id/show_letter"

android:layout_width="match_parent"

android:layout_height="wrap_content"

https://www.sodocs.net/doc/548988550.html,/ android:layout_marginTop="3dp"/>

android:layout_width="match_parent"

android:layout_height="wrap_content">

android:id="@+id/userface"

android:layout_width="72dp"

android:layout_height="72dp"

android:padding="12dp"

android:src="@mipmap/ic_launcher"/>

android:id="@+id/username"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:layout_centerVertical="true"

android:layout_marginLeft="36dp"

android:layout_toRightOf="@id/userface"

android:gravity="center"

android:text="username"/>

在我的item的布局文件中,我所有的item实际上都是一样的,都有一个显示分组数据的TextView,因此我需要在Adapter的getView方法中根据所显示的item的不同来确定是否将显示分组的TextView隐藏掉。所以我们再回过头来看看我的ListView中的getView方法,getView前面的写法没啥好说的,和普通ListView都一样,我们主要来看看这几行:

//获得当前position是属于哪个分组

intsectionForPosition = getSectionForPosition(position);

//获得该分组第一项的position

intpositionForSection = getPositionForSection(sectionForPosition);

//查看当前position是不是当前item所在分组的第一个item

//如果是,则显示showLetter,否则隐藏

if (position == positionForSection) {

holder.showLetter.setVisibility(View.VISIBLE);

holder.showLetter.setText(user.getFirstLetter());

} else {

holder.showLetter.setVisibility(View.GONE);

}

我首先判断当前显示的item是属于哪个分组的,然后获得这个分组中第一个item的位置,最后判断我当前显示的item的position到底是不是它所在分组的第一个item,如果是的话,那么就将showLetter这个TextView显示出来,同时显示出相应的分组信息,否则将这个 showLetter隐藏。就是这么简单。做完这些之后,我们在Activity中再来简单的添加两行代码:

ListViewlistView = (ListView) findViewById(R.id.lv);

MyAdapter adapter = new MyAdapter(this, list);

listView.setAdapter(adapter);

这个时候左边的分组ListView就可以显示出来了。就是这么简单。

三.右边滑动控件的实现

右边这个东东很明显是一个自定义View,那我们就一起来看看这个自定义View吧。

首先这个自定义控件继承自View,继承自View,需要实现它里边的构造方法,关于这三个构造方法的解释大家可以查看我的另一篇博客android自定义View之钟表诞生记,这里对于构造方法我不再赘述。在这个自定义View中,我需要首先声明5个变量,如下:

//当前手指滑动到的位置

private intchoosedPosition = -1;

//画文字的画笔

private Paint paint;

//右边的所有文字

private String[] letters = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L",

"M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"};

//页面正中央的TextView,用来显示手指当前滑动到的位置的文本

private TextViewtextViewDialog;

//接口变量,该接口主要用来实现当手指在右边的滑动控件上滑动时ListView能够跟着滚动

private UpdateListViewupdateListView;

五个变量的作用我在注释中已经说的很详细了。OK,变量声明完成之后,我还要初始化一些变量,变量的初始化当然放在构造方法中来进行了:

public LetterIndexView(Context context, AttributeSetattrs, intdefStyleAttr) {

super(context, attrs, defStyleAttr);

paint = new Paint();

paint.setAntiAlias(true);

paint.setTextSize(24);

}

OK,这里要初始化的实际上只有paint一个变量。

准备工作做完之后,接下来就是onDraw了,代码如下:

@Override

protected void onDraw(Canvas canvas) {

intperTextHeight = getHeight() / letters.length;

for (inti = 0; i

if (i == choosedPosition) {

paint.setColor(Color.RED);

} else {

paint.setColor(Color.BLACK);

}

canvas.drawText(letters[i], (getWidth() - paint.measureText(letters[i])) / 2, (i + 1) * perTextHeight, paint);

}

}

在绘制的时候,我需要首先获得每一个文字所占空间的大小,每一个文本的可用高度应该是总高度除以文字的总数,然后,通过一个for循环将26个字母全都画出来。在画的时候,如果这个文本所处的位置刚好就是我手指按下的位置,那么该文本的颜色为红色,否则为黑色,最后的drawText不需要我再说了吧。

绘制完成之后,就是重写onTouchEvent了,如下:

@Override

public booleanonTouchEvent(MotionEvent event) {

intperTextHeight = getHeight() / letters.length;

float y = event.getY();

intcurrentPosition = (int) (y / perTextHeight);

String letter = letters[currentPosition];

switch (event.getAction()) {

case MotionEvent.ACTION_UP:

setBackgroundColor(Color.TRANSPARENT);

if (textViewDialog != null) {

textViewDialog.setVisibility(View.GONE);

}

break;

default:

setBackgroundColor(Color.parseColor("#cccccc"));

if (currentPosition> -1 &¤tPosition

textViewDialog.setVisibility(View.VISIBLE);

textViewDialog.setText(letter);

}

if (updateListView != null) {

updateListView.updateListView(letter);

}

choosedPosition = currentPosition;

}

break;

}

invalidate();

return true;

}

对于右边的滑动控件的事件操作我整体上可以分为两部分,手指抬起分为一类,其他所有的操作归为一类。那么当控件感知到我手指的操作事件之后,它首先需要知道我手指当前所点击的item是什么,那么这个值要怎么获取呢?我可以先获得到手指所在位置的Y 坐标,然后除以每一个文字的高度,就知道当前手指点击位置的 position,然后从letters 数组中读取出相应的值即可。知道了当前点击了哪个字母之后,剩下的工作就很简单了,修改控件的背景颜色,然后将相应的字母显示在TextView上即可,然后把当前的position 传给choosedPosition,最后调用invalidate()方法重绘控件。重绘控件时由于choosedPosition的值已经发生了变化,所以相应的文本颜色也会改变。另外,我希望手指在右边控件滑动时,ListView也能跟着滚动,这个毫无疑问使用接口回调,具体大家看代码,简单的东西不赘述。最后,我希望ListView滚动时,右边控件中文本的颜色应该实时更新,那么这个也很简单,在自定义View中公开一个方法即可,如下:

public void updateLetterIndexView(intcurrentChar) {

for (inti = 0; i

if (currentChar == letters[i].charAt(0)) {

android 自定义圆角头像以及使用declare-styleable进行配置属性解析

android 自定义圆角头像以及使用declare-styleable进行配置属性解析由于最新项目中正在检查UI是否与效果图匹配,结果关于联系人模块给的默认图片是四角稍带弧度的圆角,而我们截取的图片是正方形的,现在要给应用统一替换。应用中既用到大圆角头像(即整个头像是圆的)又用到四角稍带弧度的圆角头像,封装一下以便重用。以下直接见代码 [java] view plain copy 在CODE上查看代码片派生到我的代码片 package com.test.demo; import com.test.demo.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Shader.TileMode; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.widget.ImageView; /** * 圆角imageview */ public class RoundImageView extends ImageView { private static final String TAG = "RoundImageView"; /** * 图片的类型,圆形or圆角 */ private int type; public static final int TYPE_CIRCLE = 0; public static final int TYPE_ROUND = 1; /** * 圆角大小的默认值

Android开发规范参考文档

Android开发参考文档 一、Android编码规范 1. java代码中不出现中文,最多注释中可以出现中文.xml代码中注释 2. 成员变量,局部变量、静态成员变量命名、常量(宏)命名 1). 成员变量: activity中的成员变量以m开头,后面的单词首字母大写(如Button mBackButton; String mName);实体类和自定义View的成员变量可以不以m开头(如ImageView imageView,String name), 2). 局部变量命名:只能包含字母,组合变量单词首字母出第一个外,都为大写,其他字母都为小写 3). 常量(宏)命名: 只能包含字母和_,字母全部大写,单词之间用_隔开UMENG_APP_KEY 3. Application命名 项目名称+App,如SlimApp,里面可以存放全局变量,但是杜绝存放过大的实体对象4. activity和其中的view变量命名 activity命名模式为:逻辑名称+Activity view命名模式为:逻辑名称+View 建议:如果layout文件很复杂,建议将layout分成多个模块,每个模块定义一个moduleViewHolder,其成员变量包含所属view 5. layout及其id命名规则 layout命名模式:activity_逻辑名称,或者把对应的activity的名字用“_”把单词分开。

命名模式为:view缩写_模块名称_view的逻辑名称, 用单词首字母进行缩写 view的缩写详情如下 LayoutView:lv RelativeView:rv TextView:tv ImageView:iv ImageButton:ib Button:btn 6. strings.xml中的 1). id命名模式: activity名称_功能模块名称_逻辑名称/activity名称_逻辑名称/common_逻辑名称,strings.xml中,使用activity名称注释,将文件内容区分开来 2). strings.xml中使用%1$s实现字符串的通配,合起来写 7. drawable中的图片命名 命名模式:activity名称_逻辑名称/common_逻辑名称/ic_逻辑名称 (逻辑名称: 这是一个什么样的图片,展示功能是什么) 8. styles.xml 将layout中不断重现的style提炼出通用的style通用组件,放到styles.xml中; 9. 使用layer-list和selector,主要是View onCclick onTouch等事件界面反映

Android平台我的日记设计文档

Android平台我的日记 设计文档 项目名称:mydiray 项目结构示意: 阶段任务名称(一)布局的设计 开始时间: 结束时间: 设计者: 梁凌旭 一、本次任务完成的功能 1、各控件的显示 二、最终功能及效果 三、涉及知识点介绍 四、代码设计 activity_main.xml:

android:layout_centerHorizontal="true" android:layout_marginTop="88dp" android:text="@string/wo" android:textSize="35sp"/>