首先描述下需求:
1、新增短信时,进来收件人是为空,显示一行文字(提醒)
2、从通讯录选择联系人后,回到短信编辑界面,收件栏显示一行,内容为“收件人:XXX、XXX、XXX、XXX.....”
3、当点击收件栏时,收件栏内容变化,变成可删除,最多显示四行,多余四行有上下滑动轮,不足四行,是几行显示几行
4、填写短信内容,即时计算短信条算,并有文本提示
下面是实现后效果图
下面终于到了代码实现块了,主要写一些主要用到的类,其他不太重要的就忽略写,读者自行补全。
首先是短信收件人是一个自定义的流布局,这个流布局不是本人写的,只是在上面加了些修改来满足自己的业务需求。
TagFlowLayout类
import java.util.HashSet; import java.util.Iterator; import java.util.Set; import com.zhaonongzi.wnshhseller.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; /** * 自定义tag 页面的整体页面 * * 实现绑定数据,事件处理 * @author admin * */ public class TagFlowLayout extends FlowLayout implements TagAdapter.OnDataChangedListener { private TagAdapter mTagAdapter; private boolean mAutoSelectEffect = true; private int mSelectedMax = -1;// -1为不限制数量 private static final String TAG = "TagFlowLayout"; private MotionEvent mMotionEvent; private Set<Integer> mSelectedView = new HashSet<Integer>(); public TagFlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout); mAutoSelectEffect = ta.getBoolean(R.styleable.TagFlowLayout_auto_select_effect, true); mSelectedMax = ta.getInt(R.styleable.TagFlowLayout_max_select, -1); ta.recycle(); if (mAutoSelectEffect) { setClickable(true); } } public TagFlowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TagFlowLayout(Context context) { this(context, null); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { TagView tagView = (TagView) getChildAt(i); if (tagView.getVisibility() == View.GONE) continue; if (tagView.getTagView().getVisibility() == View.GONE) { tagView.setVisibility(View.GONE); } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } public interface OnSelectListener { void onSelected(Set<Integer> selectPosSet); } private OnSelectListener mOnSelectListener; public void setOnSelectListener(OnSelectListener onSelectListener) { mOnSelectListener = onSelectListener; if (mOnSelectListener != null) setClickable(true); } public interface OnTagClickListener { boolean onTagClick(View view, int position, FlowLayout parent); } private OnTagClickListener mOnTagClickListener; public void setOnTagClickListener(OnTagClickListener onTagClickListener) { mOnTagClickListener = onTagClickListener; if (onTagClickListener != null) setClickable(true); } public void setAdapter(TagAdapter adapter) { // if (mTagAdapter == adapter) // return; mTagAdapter = adapter; mTagAdapter.setOnDataChangedListener(this); changeAdapter(); } private void changeAdapter() { removeAllViews(); TagAdapter adapter = mTagAdapter; TagView tagViewContainer = null; HashSet preCheckedList = mTagAdapter.getPreCheckedList(); for (int i = 0; i < adapter.getCount(); i++) { View tagView = adapter.getView(this, i, adapter.getItem(i)); tagViewContainer = new TagView(getContext()); // ViewGroup.MarginLayoutParams clp = (ViewGroup.MarginLayoutParams) // tagView.getLayoutParams(); // ViewGroup.MarginLayoutParams lp = new // ViewGroup.MarginLayoutParams(clp); // lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; // lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; // lp.topMargin = clp.topMargin; // lp.bottomMargin = clp.bottomMargin; // lp.leftMargin = clp.leftMargin; // lp.rightMargin = clp.rightMargin; tagView.setDuplicateParentStateEnabled(true); tagViewContainer.setLayoutParams(tagView.getLayoutParams()); tagViewContainer.addView(tagView); addView(tagViewContainer); if (preCheckedList.contains(i)) { tagViewContainer.setChecked(true); } } mSelectedView.addAll(preCheckedList); } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { mMotionEvent = MotionEvent.obtain(event); } return super.onTouchEvent(event); } @Override public boolean performClick() { if (mMotionEvent == null) return super.performClick(); int x = (int) mMotionEvent.getX(); int y = (int) mMotionEvent.getY(); mMotionEvent = null; TagView child = findChild(x, y); int pos = findPosByView(child); if (child != null) { doSelect(child, pos); if (mOnTagClickListener != null) { return mOnTagClickListener.onTagClick(child.getTagView(), pos, this); } } return super.performClick(); } public void setMaxSelectCount(int count) { if (mSelectedView.size() > count) { Log.w(TAG, "you has already select more than " + count + " views , so it will be clear ."); mSelectedView.clear(); } mSelectedMax = count; } public Set<Integer> getSelectedList() { return new HashSet<Integer>(mSelectedView); } private void doSelect(TagView child, int position) { if (mAutoSelectEffect) { if (!child.isChecked()) { // 处理max_select=1的情况 if (mSelectedMax == 1 && mSelectedView.size() == 1) { Iterator<Integer> iterator = mSelectedView.iterator(); Integer preIndex = iterator.next(); TagView pre = (TagView) getChildAt(preIndex); pre.setChecked(false); child.setChecked(true); mSelectedView.remove(preIndex); mSelectedView.add(position); } else { if (mSelectedMax > 0 && mSelectedView.size() >= mSelectedMax) return; child.setChecked(true); mSelectedView.add(position); } } else { child.setChecked(false); mSelectedView.remove(position); } if (mOnSelectListener != null) { mOnSelectListener .onSelected(new HashSet<Integer>(mSelectedView)); } } } private static final String KEY_CHOOSE_POS = "key_choose_pos"; private static final String KEY_DEFAULT = "key_default"; @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable(KEY_DEFAULT, super.onSaveInstanceState()); String selectPos = ""; if (mSelectedView.size() > 0) { for (int key : mSelectedView) { selectPos += key + "|"; } selectPos = selectPos.substring(0, selectPos.length() - 1); } bundle.putString(KEY_CHOOSE_POS, selectPos); return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; String mSelectPos = bundle.getString(KEY_CHOOSE_POS); if (!TextUtils.isEmpty(mSelectPos)) { String[] split = mSelectPos.split("\\|"); for (String pos : split) { int index = Integer.parseInt(pos); mSelectedView.add(index); TagView tagView = (TagView) getChildAt(index); tagView.setChecked(true); } } super.onRestoreInstanceState(bundle.getParcelable(KEY_DEFAULT)); return; } super.onRestoreInstanceState(state); } private int findPosByView(View child) { final int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { View v = getChildAt(i); if (v == child) return i; } return -1; } private TagView findChild(int x, int y) { final int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { TagView v = (TagView) getChildAt(i); if (v.getVisibility() == View.GONE) continue; Rect outRect = new Rect(); v.getHitRect(outRect); if (outRect.contains(x, y)) { return v; } } return null; } @Override public void onChanged() { changeAdapter(); } }
FlowLayout类
import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; import java.util.List; import com.zhaonongzi.wnshhseller.activity.customer.AddMessageActivity; import com.zhaonongzi.wnshhseller.utils.GlobalMemoryCache; /** * Tag 页面布局基类 主要实现测量子view,绘制view * * @author admin * */ public class FlowLayout extends ViewGroup { protected List<List<View>> mAllViews = new ArrayList<List<View>>(); protected List<Integer> mLineHeight = new ArrayList<Integer>(); private int lines, counts, lastItem;// 限制显示多少行 private boolean isShow;// 是否需要在最后显示省略号 private LastListerInterface lastListerInterface = null; private int ihegth; public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public FlowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FlowLayout(Context context) { this(context, null); } public void setLines(int line) { this.lines = line; } public int getLastItem() { Log.e("lastItem", lastItem + ""); return lastItem; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); // wrap_content int width = 0; int height = 0; int lineWidth = 0; int lineHeight = 0; int childHeight1 = 0; int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { View child = getChildAt(i); if (child.getVisibility() == View.GONE) { if (i == cCount - 1) { width = Math.max(lineWidth, width); height += lineHeight; } continue; } measureChild(child, widthMeasureSpec, heightMeasureSpec); MarginLayoutParams lp = (MarginLayoutParams) child .getLayoutParams(); int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; childHeight1 = childHeight; if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) { width = Math.max(width, lineWidth); lineWidth = childWidth; height += lineHeight; lineHeight = childHeight; } else { lineWidth += childWidth; lineHeight = Math.max(lineHeight, childHeight); } if (i == cCount - 1) { width = Math.max(lineWidth, width); height += lineHeight; } if (lastItem == 0 && height == (3 * childHeight)) { lastItem = i - 1; if (lastItem > 0 && null != lastListerInterface) { lastListerInterface.getLastItem(lastItem); } } // Log.e("lastItem", lastItem + ""); } height = (height <= (lines * childHeight1)) ? height : (lines * childHeight1); ihegth = height; AddMessageActivity.iheight = childHeight1; setMeasuredDimension( // modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(), modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()// ); } public int getHeigth() { return ihegth; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mAllViews.clear(); mLineHeight.clear(); int width = getWidth(); int lineWidth = 0; int lineHeight = 0; List<View> lineViews = new ArrayList<View>(); int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { View child = getChildAt(i); if (child.getVisibility() == View.GONE) continue; MarginLayoutParams lp = (MarginLayoutParams) child .getLayoutParams(); int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) { if (mAllViews.size() < lines) { mLineHeight.add(lineHeight); mAllViews.add(lineViews); lineWidth = 0; lineHeight = childHeight + lp.topMargin + lp.bottomMargin; lineViews = new ArrayList<View>(); } } lineWidth += childWidth + lp.leftMargin + lp.rightMargin; lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin); lineViews.add(child); } mLineHeight.add(lineHeight); mAllViews.add(lineViews); int left = getPaddingLeft(); int top = getPaddingTop(); int lineNum = mAllViews.size(); AddMessageActivity.ilines = lineNum; if (lines > 0) { lineNum = lineNum > lines ? lines : lineNum; } for (int i = 0; i < lineNum; i++) { lineViews = mAllViews.get(i); lineHeight = mLineHeight.get(i); for (int j = 0; j < lineViews.size(); j++) { View child = lineViews.get(j); if (child.getVisibility() == View.GONE) { continue; } MarginLayoutParams lp = (MarginLayoutParams) child .getLayoutParams(); int lc = left + lp.leftMargin; int tc = top + lp.topMargin; int rc = lc + child.getMeasuredWidth(); int bc = tc + child.getMeasuredHeight(); child.layout(lc, tc, rc, bc); left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; } left = getPaddingLeft(); top += lineHeight; } if ((boolean) GlobalMemoryCache.getInstance().get("addMessage")) { AddMessageActivity.tfl_add_message_labellay .setVisibility(View.GONE); AddMessageActivity.scr_add_message_labellay .setVisibility(View.GONE); } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @Override protected LayoutParams generateLayoutParams(LayoutParams p) { return new MarginLayoutParams(p); } public interface LastListerInterface { public void getLastItem(int lastItem); } public void setLastListener(LastListerInterface lastListerInterface) { this.lastListerInterface = lastListerInterface; } }
TagAdapter类
import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import android.view.View; /** * tag 布局适配器 * @author admin * * @param <T> */ public abstract class TagAdapter<T> { private List<T> mTagDatas; private OnDataChangedListener mOnDataChangedListener; private HashSet<Integer> mCheckedPosList = new HashSet<Integer>(); public TagAdapter(List<T> datas) { mTagDatas = datas; } public TagAdapter(T[] datas) { mTagDatas = new ArrayList<T>(Arrays.asList(datas)); } static interface OnDataChangedListener { void onChanged(); } void setOnDataChangedListener(OnDataChangedListener listener) { mOnDataChangedListener = listener; } public void setSelectedList(int... pos) { for (int i = 0; i < pos.length; i++) mCheckedPosList.add(pos[i]); notifyDataChanged(); } HashSet<Integer> getPreCheckedList() { return mCheckedPosList; } public int getCount() { return mTagDatas == null ? 0 : mTagDatas.size(); } public void notifyDataChanged() { mOnDataChangedListener.onChanged(); } public T getItem(int position) { return mTagDatas.get(position); } public abstract View getView(FlowLayout parent, int position, T t); }
TagView类
import android.content.Context; import android.view.View; import android.widget.Checkable; import android.widget.FrameLayout; /** * 单个Tag 的自定义布局 * * @author admin * */ public class TagView extends FrameLayout implements Checkable { private boolean isChecked; private static final int[] CHECK_STATE = new int[]{android.R.attr.state_checked}; public TagView(Context context) { super(context); } public View getTagView() { return getChildAt(0); } @Override public int[] onCreateDrawableState(int extraSpace) { int[] states = super.onCreateDrawableState(extraSpace + 1); if (isChecked()) { mergeDrawableStates(states, CHECK_STATE); } return states; } /** * Change the checked state of the view * * @param checked The new checked state */ @Override public void setChecked(boolean checked) { if (this.isChecked != checked) { this.isChecked = checked; refreshDrawableState(); } } /** * @return The current checked state of the view */ @Override public boolean isChecked() { return isChecked; } /** * Change the checked state of the view to the inverse of its current state */ @Override public void toggle() { setChecked(!isChecked); } }
上面是用到TagFlowLayout这个自定义控件需要的类。
下面给出短信编辑的布局xml文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tag="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#f5f5f5" android:focusable="true" android:focusableInTouchMode="true" android:orientation="vertical" > <com.zhaonongzi.wnshhseller.view.TopBar android:id="@+id/message_mass_top" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center|top" /> <LinearLayout android:id="@+id/linear_edit_message_add_receiver" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@+id/message_mass_top" android:orientation="horizontal" > <TextView android:id="@+id/txt_add_message_customer_declare" android:layout_width="wrap_content" android:layout_height="45dp" android:gravity="center" android:paddingLeft="15dp" android:text="收信人" android:textColor="@color/black" android:visibility="gone" /> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="1" > <TextView android:id="@+id/txt_add_message_add_customer_btn" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="left|center" android:paddingLeft="15dp" android:singleLine="true" android:text="点击添加收信客户" android:textColor="@color/gray" /> <ScrollView android:id="@+id/scr_add_message_labellay" android:layout_width="fill_parent" android:layout_height="90dp" android:visibility="gone" > <com.zhaonongzi.wnshhseller.view.flowlayout.TagFlowLayout android:id="@+id/tfl_add_message_labellay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/ac_include" android:layout_marginTop="4dp" android:layout_weight="1" android:paddingLeft="10dp" android:paddingRight="10dp" android:visibility="gone" tag:max_select="-1" /> </ScrollView> </FrameLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:paddingLeft="10dp" android:paddingRight="15dp" android:layout_gravity="center" android:paddingTop="5dp" android:paddingBottom="5dp"> <ImageView android:id="@+id/img_add_message_add_customer" android:layout_width="30dp" android:layout_height="30dp" android:layout_gravity="center" android:src="@drawable/username" /> <TextView android:id="@+id/txt_add_message_customer_counts" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:textColor="@color/black" android:text="0" /> </LinearLayout> </LinearLayout> <View android:id="@+id/line_1" android:layout_width="fill_parent" android:layout_height="1dp" android:layout_below="@+id/linear_edit_message_add_receiver" android:layout_marginBottom="2dp" android:layout_marginTop="2dp" android:background="#A0A0A0" /> <EditText android:id="@+id/edittext_add_message_add_content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@+id/line_1" android:background="@drawable/select_white_bg" android:minHeight="80dp" android:padding="15dp" android:maxLength="650" android:scrollbars="vertical" android:maxHeight="200dp" android:textColor="@color/black" /> <TextView android:id="@+id/txt_add_message_add_content_num" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/edittext_add_message_add_content" android:layout_toLeftOf="@+id/txt_add_message_add_content_word" android:paddingTop="10dp" android:text="0" android:textColor="@color/orange_title" /> <TextView android:id="@+id/txt_add_message_add_content_word" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@+id/edittext_add_message_add_content" android:paddingRight="15dp" android:paddingTop="10dp" android:text="" android:textColor="@color/gray" /> </RelativeLayout>
实例化一个收件人adapter
adapter = new TagAdapter<String>(meBean.getReceiver()) { final LayoutInflater mInflater = LayoutInflater .from(AddMessageActivity.this); @SuppressLint("NewApi") @Override public View getView(FlowLayout parent, final int position, final String s) { final TextView tv = (TextView) mInflater.inflate( R.layout.item_label_tv_medium, tfl_add_message_labellay, false); SpannableStringBuilder sp1 = new SpannableStringBuilder( s);// 姓名 SpannableStringBuilder sp2 = new SpannableStringBuilder( "x");// 删除 sp1.setSpan( new ForegroundColorSpan(Color .rgb(102, 102, 102)), 0, s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); sp2.setSpan( new ForegroundColorSpan(Color.rgb(255, 0, 0)), 0, "X".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); SpannableStringBuilder sp = new SpannableStringBuilder();// sp.append(sp1).append(sp2); if (position == 0) { tv.setText(s); tv.setTextColor(android.graphics.Color .parseColor("#666666")); } else { tv.setText(sp); tv.setTextSize(16); tv.setBackgroundResource(R.drawable.selector_contacts_gray_btn); } tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 跳页面带值 if (position != 0) { meBean.getReceiver().remove(position); handler.sendEmptyMessage(1); } } }); return tv; } };
计算短信条数方法:
/** * 计算短信内容条算 * * @param wordNum * @return */ public int getSMSCounts(int wordNum) { int counts; if (wordNum <= 63) counts = 1; else if (wordNum <= 127) counts = 2; else { counts = (wordNum - 127) % 67 == 0 ? ((wordNum - 127) / 67 + 2) : ((wordNum + 7) / 67 + 1); } return counts; }
监听编辑文本框来提示短信条数和字数:
edittext_edit_message_add_content .addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // TODO Auto-generated method stub } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { if (getSMSCounts(edittext_edit_message_add_content .getText().toString().trim().length()) == 1) { int c = 63 - edittext_edit_message_add_content .getText().toString().trim().length(); txt_add_message_add_content_num.setText(c + ""); txt_add_message_add_content_word.setText(""); } else if (getSMSCounts(edittext_edit_message_add_content .getText().toString().trim().length()) == 2) { int c = 127 - edittext_edit_message_add_content .getText().toString().trim().length(); txt_add_message_add_content_num.setText(c + ""); txt_add_message_add_content_word.setText("(2)"); } else if (getSMSCounts(edittext_edit_message_add_content .getText().toString().trim().length()) > 2) { int c = getSMSCounts(edittext_edit_message_add_content .getText().toString().trim().length()) * 67 - 7 - edittext_edit_message_add_content .getText().toString().trim() .length(); txt_add_message_add_content_num.setText(c + ""); txt_add_message_add_content_word .setText("(" + getSMSCounts(edittext_edit_message_add_content .getText().toString() .trim().length()) + ")"); } } });
接收删除后的handler处理:
private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { if (msg.what == 1) { tfl_add_message_labellay.onChanged(); txt_add_message_customer_counts.setText((meBean.getReceiver() .size() - 1) + ""); } }; };
上面已经把关键的代码都贴出来了,其实最重要的 地方是按流布局去显示收件人,原生的是没有限制显示行数,也没有处理多行后的滑动轮,头疼的地方是去计算显示高度并动态设置scrollview的高地。坑都已经踩过了,但是这个代码的效率不是很高,有待继续完善,或者有什么好的建议也可以给我提。
时间: 2024-10-10 21:45:18