一、打造一个带横格子的输入控件
多行文本样式的输入界面,我们首先联想到的是EditText,这一点问题都没有,但EditText貌似不能满足需求,需要实现每一行下面都带有一个横线,这就像我们小时候的横格子作业本。那么自然想到的是继承控件EditText去自定义控件,还是没有具体的实现想法。
我们知道,google android给我们的例子里面记事本NotePad其实是有实现这个功能的,那就赶紧去看看他是如何实现的吧,到这个路径下(X盘:\android-sdk-windows\samples\android-16\NotePad),这个就是对应的例程了,导入到eclipse。工程结构如下:
很明显的可以从类名知道NoteEditor.java就是我们要找的类,编辑文本的界面,在里面我们找到了如下带横线的EditText实现:
/**
* Defines a custom EditText View that draws lines between each line of text that is displayed.
*/
public static class LinedEditText extends EditText {
private Rect mRect;
private Paint mPaint;
// This constructor is used by LayoutInflater
public LinedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
// Creates a Rect and a Paint object, and sets the style and color of the Paint object.
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0x800000FF);
}
/**
* This is called to draw the LinedEditText object
* @param canvas The canvas on which the background is drawn.
*/
@Override
protected void onDraw(Canvas canvas) {
// Gets the number of lines of text in the View.
int count = getLineCount();
// Gets the global Rect and Paint objects
Rect r = mRect;
Paint paint = mPaint;
/*
* Draws one line in the rectangle for every line of text in the EditText
*/
for (int i = 0; i < count; i++) {
// Gets the baseline coordinates for the current line of text
int baseline = getLineBounds(i, r);
/*
* Draws a line in the background from the left of the rectangle to the right,
* at a vertical position one dip below the baseline, using the "paint" object
* for details.
*/
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
}
// Finishes up by calling the parent method
super.onDraw(canvas);
}
}
对应的布局如下:
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="com.example.android.notepad.NoteEditor$LinedEditText"
android:id="@+id/note"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:padding="5dp"
android:scrollbars="vertical"
android:fadingEdge="vertical"
android:gravity="top"
android:textSize="22sp"
android:capitalize="sentences"
/>
原生这个代码实现,优化的不是特别好,我们继续考虑一下边界问题,优化一下:
/**
* @Description TODO 带下划线的EditText,实现每行下面都显示横线
*/
public class DividerEditText extends EditText {
private static final int MARGIN = 0;
private Rect mRect;
private Paint mPaint;
// This constructor is used by LayoutInflater
public DividerEditText(Context context, AttributeSet attrs) {
super(context, attrs);
// Creates a Rect and a Paint object, and sets the style and color of
// the Paint object.
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(3.0f);
mPaint.setColor(0x800000FF);
}
/**
* This is called to draw the DividerEditText object
*
* @param canvas
* The canvas on which the background is drawn.
*/
@Override
protected void onDraw(Canvas canvas) {
// Gets the global Rect and Paint objects
Rect r = mRect;
Paint paint = mPaint;
Layout layout = getLayout();
if (!canvas.getClipBounds(r)) {
return;
}
float startX = r.left + MARGIN, stopX = r.right - MARGIN;
int count = layout.getLineCount();
int lineHeight = getLineHeight();
int height = getHeight() - getPaddingBottom() - getPaddingTop();
int n = height % lineHeight == 0 ? height / lineHeight : height / lineHeight + 1;
if (count < n) {
count = n;
}
for (int i = 1; i <= count; i++) {
int y = lineHeight * i;
canvas.drawLine(startX, y, stopX, y, paint);
}
// Finishes up by calling the parent method
super.onDraw(canvas);
}
}
代码很简单,就是考虑一下边界的布局问题,没什么好说的。
二、弹出样式的多行文本输入界面实现
首先我们可以想到PopupWindow、Dialog,直接浮于Activity之上,那我就来按照自己想法实现一下。我这里都是横向布局实现,纵向没有测试。
1.使用PopupWindow
@SuppressLint("ViewConstructor")
public class PopupWindowNote extends PopupWindow {
private int width = 0;
private int height = 0;
private DividerEditText dividerEditText;
@SuppressWarnings("deprecation")
public PopupWindowNote(Context context, String content, final OnPopupWindowNoteClickListener onPopupWindowNoteClickListener) {
super(context);
LayoutInflater inflater = LayoutInflater.from(context);
View contentView = inflater.inflate(R.layout.dialog_note, null);
setContentView(contentView);
Button cancelBtn = (Button) contentView.findViewById(R.id.btn_cancel_popupwindow_note);
Button okBtn = (Button) contentView.findViewById(R.id.btn_ok_popupwindow_note);
dividerEditText = (DividerEditText) contentView.findViewById(R.id.det_content_popupwindow_note);
cancelBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onPopupWindowNoteClickListener.onPopupWindowNoteCancelBtnClick();
}
});
okBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onPopupWindowNoteClickListener.onPopupWindowNoteOkBtnClick();
}
});
dividerEditText.setText("" + content);
setWidth(ScreenUtil.getScreenWidth(context));
setHeight(ScreenUtil.getScreenHeight(context));
setAnimationStyle(R.style.AnimStylePushInAndPushOut);
setBackgroundDrawable(new ColorDrawable(0));
setFocusable(true);
setOutsideTouchable(true);
// 点击“返回Back”消失,并且不影响背景元素
setBackgroundDrawable(new BitmapDrawable());
}
public int getPopupWindowWidth() {
return width;
}
public void setPopupWindowWidth(int width) {
this.width = width;
setWidth(this.width);
this.update(this.width, this.height);
}
public int getPopupWindowHeight() {
return height;
}
public void setPopupWindowHeight(int height) {
this.height = height;
setHeight(this.height);
this.update(this.width, this.height);
}
public String getPopupWindowNoteString() {
return dividerEditText.getText().toString().trim();
}
// PopupWindowNote回调接口
public interface OnPopupWindowNoteClickListener {
public void onPopupWindowNoteOkBtnClick();
public void onPopupWindowNoteCancelBtnClick();
}
}
内部实现了一个回调函数,用于监听PopupWindow上的按钮点击事件,getPopupWindowNoteString()用于获取editText上用户输入的文本内容,供Activity使用。设置editText上的内容可以通过构造函数传入。
使用方法:
private PopupWindowNote popupWindowNote;
......
//前提需要你的Activity实现接口OnPopupWindowNoteClickListener
popupWindowNote = new PopupWindowNote(context, "我是测试哇哈哈",this);
......
//代码中弹出调用
popupWindowNote.showAtLocation(btnInfo, Gravity.CENTER, 0, 0);
......
//activity销毁注意调用一下
@Override
public void onDestroy() {
<span style="white-space:pre"> </span>super.onDestroy();
if (popupWindowNote.isShowing()) {
popupWindowNote.dismiss();
}
}
本来以为大工搞成运行一下看看效果吧。布局有问题,按钮没有显示全,怎么才能显示全呢,只能通过margin属性控制了。这都不是大问题,输入文字试试,当多行文字输入后滚动向下,直接崩溃了。而且无法调起来文本自带的文本复制功能,这也是为什么我放弃了使用PopupWindow实现这个功能了。
看来使用PopupWindow实现文本输入,问题多多,为了功能的稳定决定放弃。
2.使用Dialog方式
PopupWindow有的问题在这里统统解决了,看代码。
public class DialogNote {
private String note;
private Context context;
private DividerEditText dividerEditText;
public DialogNote(String note, Context context) {
super();
this.note = note;
this.context = context;
}
public void show(final DialogNoteClickListener dialogNoteClickListener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
final AlertDialog alertDialog = builder.create();
alertDialog.show();
Window win = alertDialog.getWindow();
//设置对话框满屏
win.setLayout(ScreenUtil.getScreenWidth(context), ScreenUtil.getScreenHeight(context));
win.setWindowAnimations(R.style.AnimStylePushInAndPushOut);
LayoutInflater factory = LayoutInflater.from(context);
final View view = factory.inflate(R.layout.dialog_note, null);
win.setContentView(view);
//实现弹出软键盘
win.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
Button cancelBtn = (Button) win.findViewById(R.id.btn_cancel_popupwindow_note);
Button okBtn = (Button) win.findViewById(R.id.btn_ok_popupwindow_note);
dividerEditText = (DividerEditText) win.findViewById(R.id.det_content_popupwindow_note);
cancelBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialogNoteClickListener.onDialogNoteCancelBtnClick();
alertDialog.dismiss();
}
});
okBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialogNoteClickListener.onDialogNoteOkBtnClick();
alertDialog.dismiss();
}
});
dividerEditText.setText(note);
}
public String getNote() {
note = dividerEditText.getText().toString().trim() + "\0";
return note;
}
public interface DialogNoteClickListener {
public void onDialogNoteOkBtnClick();
public void onDialogNoteCancelBtnClick();
}
}
和popup类似,实现了相关借口,使用方法类同。
private DialogNote dialogNote;
......
//初始化
dialogNote = new DialogNote("我是测试数据哇", context);
//调用弹出
dialogNote.show(xxxActivity.this);
.......
看看效果如何:
布局正常,不会崩溃,可正常调起系统的复制粘贴功能,还算比较满意。
通过AlertDialog搭建的对话框,你会发现不能正常弹出输入法,解决办法:
//实现弹出软键盘
win.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
直接继承Dialog创建对话框,听说是没有问题的,这个没有测试。
3.使用到的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/offset_6dp"
android:layout_marginRight="@dimen/offset_6dp"
android:layout_marginTop="@dimen/offset_6dp" >
<Button
android:id="@+id/btn_cancel_popupwindow_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="cancel"
android:textColor="@android:color/black"
android:textSize="@dimen/textsize_level_10" />
<Button
android:id="@+id/btn_ok_popupwindow_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="ok"
android:textColor="@color/black"
android:textSize="@dimen/textsize_level_10" />
</RelativeLayout>
<com.xxx.widget.DividerEditText
android:id="@+id/det_content_popupwindow_note"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/offset_6dp"
android:layout_marginRight="@dimen/offset_6dp"
android:background="@null"
android:gravity="top"
android:lines="10"
android:imeOptions="flagNoExtractUi"
android:scrollbars="none"
android:textColor="@color/black"
android:textSize="@dimen/textsize_level_10" >
</com.xxx.widget.DividerEditText>
</LinearLayout>