前段时间遇到快速索引栏被挤压的问题,就做了个demo来研究。
先描述下问题,就是就是在一个联系人的界面中,布局中有列表,一个展示联系人,一个做为字母快速索引。
现在的问题是,
当在搜索框输入时,由于弹出软键盘,导致整体布局上移,从而出现快速索引栏被挤压,导致各字母互相重叠,界面乱了。
如下图:
布局文件是这样的:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ll_find">
<TextView
android:id="@+id/tv_find"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="搜索:"/>
<EditText
android:id="@+id/et_find_contact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="请输入联系人"
android:layout_toRightOf="@id/tv_find"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/city_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="@id/ll_find"
android:orientation="horizontal" >
<ListView android:id="@+id/list_view"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:scrollbars="vertical"
android:cacheColorHint="#00000000" />
<com.droid.MyLetterListView
android:id="@+id/MyLetterListView01"
android:background="#40000000"
android:layout_width="30dip"
android:layout_height="wrap_content"
android:layout_alignParentRight="true" />
</LinearLayout>
</LinearLayout>
MyLetterListView是一个自定义View,是网上一个前辈做的。
实现代码如下:
import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.text.style.TypefaceSpan; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class MyLetterListView extends View { OnTouchingLetterChangedListener onTouchingLetterChangedListener; String[] b = {"#","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"}; int choose = -1; Paint paint = new Paint(); boolean showBkg = false; public MyLetterListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public MyLetterListView(Context context, AttributeSet attrs) { super(context, attrs); } public MyLetterListView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(showBkg){ canvas.drawColor(Color.parseColor("#40000000")); } int height = getHeight(); int width = getWidth(); int singleHeight = height / b.length; for(int i=0;i<b.length;i++){ paint.setColor(Color.WHITE); paint.setTypeface(Typeface.DEFAULT_BOLD); paint.setAntiAlias(true); if(i == choose){ paint.setColor(Color.parseColor("#3399ff")); 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(); } } @Override public boolean dispatchTouchEvent(MotionEvent event) { final int action = event.getAction(); final float y = event.getY(); final int oldChoose = choose; final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener; final int c = (int) (y/getHeight()*b.length); switch (action) { case MotionEvent.ACTION_DOWN: showBkg = true; if(oldChoose != c && listener != null){ if(c > 0 && c< b.length){ listener.onTouchingLetterChanged(b[c]); choose = c; invalidate(); } } break; case MotionEvent.ACTION_MOVE: if(oldChoose != c && listener != null){ if(c > 0 && c< b.length){ listener.onTouchingLetterChanged(b[c]); choose = c; invalidate(); } } break; case MotionEvent.ACTION_UP: showBkg = false; choose = -1; invalidate(); break; } return true; } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } public void setOnTouchingLetterChangedListener( OnTouchingLetterChangedListener onTouchingLetterChangedListener) { this.onTouchingLetterChangedListener = onTouchingLetterChangedListener; } public interface OnTouchingLetterChangedListener{ public void onTouchingLetterChanged(String s); } } 我想到的第一个方法,是保持一个最小高度,以避免字母互相叠加导致看不清了。 可是直接可是布局文件中设置minHeigth,并没有效果。 那么就应该是这个值没有在自定义View中使用。 解决办法,就是在 onDraw() 方法里面,获取在xml布局中设置的最小高度值: int minHeight = getSuggestedMinimumHeight(); 在计算View的高度时,添加上这个限制,保证无论怎么切换,都保持高度值大于或等于最小高度,则可以避免互相重叠的问题了。 这个最小值,通过两三次运行调试,就可以大致确定出来,可以保证每个字母都能完整显示。 但是,实测发现,最初的显示高度,与弹出键盘后设置的最小高度值不一致,从而发现字母快速索引栏在变化,感觉有些不自然。 如何让界面显得很自然呢?最好是感觉不到字母快速索引栏的变化! 怎么办? 也很简单,就是记忆住View在第一次展示时的高度值,后面沿用即可。 主要是修改 onDraw()与dispatchTouchEvent() ,修改后的代码如下:
import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.text.style.TypefaceSpan; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class MyLetterListView extends View { OnTouchingLetterChangedListener onTouchingLetterChangedListener; String[] b = {"#","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"}; int choose = -1; Paint paint = new Paint(); boolean showBkg = false; int listViewHeight=0; public MyLetterListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public MyLetterListView(Context context, AttributeSet attrs) { super(context, attrs); } public MyLetterListView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(showBkg){ canvas.drawColor(Color.parseColor("#40000000")); } int height = getHeight();// 获取对应高度 int width = getWidth();// 获取对应宽度 int minHeight = getSuggestedMinimumHeight(); LogUtil.logWithMethod(new Exception(),"height="+height+" minHeight="+minHeight); if(false) { //会出现字母索引挤压问题--弹出软键盘时 listViewHeight = height; } else { //不会出现字母索引挤压问题 //保证最小高度值 if (height < minHeight) { height = minHeight; } else { // listViewHeight = height; } //记住获得的最大高度值,只增不减 if (listViewHeight < height) { listViewHeight = height; } } LogUtil.logWithMethod(new Exception(),"listViewHeight="+listViewHeight); int singleHeight = listViewHeight / b.length;// 获取每一个字母的高度 // int singleHeight = getHeight() / b.length;// 获取每一个字母的高度 for(int i=0;i<b.length;i++){ paint.setColor(Color.WHITE); paint.setTextSize(30); paint.setTypeface(Typeface.DEFAULT_BOLD); paint.setAntiAlias(true); // 选中的状态 if (i == choose) { paint.setColor(Color.parseColor("#3399ff")); paint.setFakeBoldText(true); } // x坐标等于中间-字符串宽度的一半. float xPos = width / 2 - paint.measureText(b[i]) / 2; float yPos = singleHeight * i + singleHeight; canvas.drawText(b[i], xPos, yPos, paint); paint.reset();// 重置画笔 } } @Override public boolean dispatchTouchEvent(MotionEvent event) { final int action = event.getAction(); final float y = event.getY();// 点击y坐标 final int oldChoose = choose; final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener; // final int c = (int) (y / getHeight() * b.length);// 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数. final int c = (int) ( (y* b.length) / listViewHeight );// 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数. LogUtil.logWithMethod(new Exception(),"y="+y+" b.length="+b.length+" listViewHeight="+listViewHeight); switch (action) { case MotionEvent.ACTION_DOWN: showBkg = true; if (oldChoose != c && listener != null) { if (c > 0 && c < b.length) { LogUtil.logWithMethod(new Exception(),"c="+c); listener.onTouchingLetterChanged(b[c]); choose = c; invalidate(); } } break; case MotionEvent.ACTION_MOVE: if (oldChoose != c && listener != null) { if (c > 0 && c < b.length) { listener.onTouchingLetterChanged(b[c]); choose = c; invalidate(); } } break; case MotionEvent.ACTION_UP: showBkg = false; choose = -1; invalidate(); break; } return true; } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } public void setOnTouchingLetterChangedListener( OnTouchingLetterChangedListener onTouchingLetterChangedListener) { this.onTouchingLetterChangedListener = onTouchingLetterChangedListener; } public interface OnTouchingLetterChangedListener{ public void onTouchingLetterChanged(String s); } }
这样,就完美的解决了字母挤压的问题了。
不过,后来又发现了另外的一种解决方案,并且非常简单!
就是上面的xml布局文件中,使用RelativeLayout 代替 LinearLayout 即可。
修改后的布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ll_find">
<TextView
android:id="@+id/tv_find"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="搜索:"/>
<EditText
android:id="@+id/et_find_contact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="请输入联系人"
android:layout_toRightOf="@id/tv_find"
/>
</LinearLayout>
<RelativeLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ListView android:id="@+id/list_view"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:scrollbars="none"
android:cacheColorHint="#00000000" />
<com.droid.MyLetterListView
android:id="@+id/MyLetterListView01"
android:background="#40000000"
android:layout_width="30dip"
android:layout_height="fill_parent"
android:layout_alignParentRight="true" />
</RelativeLayout>
</LinearLayout>
完整的工程,见如下地址:
http://download.youkuaiyun.com/download/lintax/10043708
参考:
http://blog.youkuaiyun.com/zpp119/article/details/7976139
http://www.javaapk.com/topics/demo/5894.html
http://www.2cto.com/kf/201311/258190.html
总结一下,本demo相比较于原型,有几点改进:
1,两个列表View的布局,不能互相重叠,从而避免点击快速索引栏时左侧的联系人被点击;
2,点击输入框,弹出软键盘时,索引ListView的压缩,导致字符重叠;
1,使用最小高度值,以保护,避免字符重叠;
2,记忆弹出软键盘之前的高度值,视觉效果上保持一致;
3,使用开关,能切换使用手机的联系人或读取一个固定的联系人文件(方便演示与测试);
4,使用开关,可切换到挤压状态。