Android EditText 光标选择 clickablespan 文字触摸

最近因项目需求,使用到CloudEditText 来实现文字输入,并且需要点击改变ImageSpan背景,使用软键盘进行删除操作


先说明一下原理,CloudEditText 是使用 SpannableString 来进行插入带有样式的文字,主要分3层:


1.SpannableString 必须有字符串传入,不然后续的插入ImageSpan 与 ClickableSpan 都会出现数组越界问题,因为没有字符串的插入,EditText本身就是空的

   SpannableString spannableString=new SpannableString(getText())


2.ImageSpan 插入drawable 到 对应字符串长度的区间

   spannableString.setSpan(imageSpan,start,end,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)


3.ClickableSpan 插入同ImageSpan 相同的区间 

   spannableString.setSpan(clickSpan,start,end,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

   想要ClickableSpan 触发点击事件,edittext.setMovementMethod(LinkMovementMethod.getInstance()) 这句话是必须要加的


说说这里边会遇到的一些坑,


1.点击事件位置偏移问题

   问题主要出在LinkMovementMethod 这个类中的OnTouchEvent 方法中,源码里边是这样写的 int off =layout.getOffsetForHorizontal(line,x) ,经查阅此方法返回值是最接近手指触摸位置的偏移量,这里就会出现一个问题,当手指触摸到某个字符最前边位置时,光标会选中在字符前边,此时去删除的话,必然会删掉前一个字符。

   之后在API中找到根据位置获取x轴位移的方法,layout.getPrimaryHorizontal(off) ,解决方案如下:

float xLeft=layout.getPrimaryHorizontal(off);
if(xLeft<x){
    off+=1;
}else{
    off-=1;
}

ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);

   2.点击EditText空白区域会选中最后一个span,这个简单,判断用户触摸位置在行宽之后,不去触发ClickableSpan的点击事件就好了

if(x<layout.getLineWidth(line)&&x>0){
    link[0].onClick(widget);
}


   3.Nexus系列原生键盘无法正确使用删除键(View.OnKeyListener 中监听删除键,原则上span 禁用系统删除,字符让系统处理

@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    return new BackInputConnection(super.onCreateInputConnection(outAttrs),true);
}

private class BackInputConnection extends InputConnectionWrapper {
    public BackInputConnection(InputConnection target, boolean mutable) {
        super(target, mutable);
    }
    @Override
    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
        if (beforeLength == 1 && afterLength == 0) {
            return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
                    KeyEvent.KEYCODE_DEL))
                    && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
                    KeyEvent.KEYCODE_DEL));
        }
        return super.deleteSurroundingText(beforeLength, afterLength);
    }
}

     4.textMultiLine 多行时,软键盘无法响应action done (TextView.OnEditorActionListener中监听)

@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    InputConnection connection=super.onCreateInputConnection(outAttrs);

    int imeActions = outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION;
    if ((imeActions&EditorInfo.IME_ACTION_DONE) != 0) {
        outAttrs.imeOptions ^= imeActions;// clear the existing action
        outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;// set the done action
    }
    if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
        outAttrs.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
    }
    return new GoogleInputConnection(connection,true);
}

     5.setSelection(index) 方法无效

this.post(new Runnable() {
    @Override
    public void run() {
        setSelection(getText().length());
    }
});

   6.imagespan 无法居中,只能有文字下边缘对齐(重写imagespan)

public int getSize(Paint paint, CharSequence text, int start, int end,
                   Paint.FontMetricsInt fontMetricsInt) {
    Drawable drawable = getDrawable();
    Rect rect = drawable.getBounds();
    if (fontMetricsInt != null) {
        Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
        int fontHeight = fmPaint.bottom - fmPaint.top;
        int drHeight = rect.bottom - rect.top;

        int top = drHeight / 2 - fontHeight / 4;
        int bottom = drHeight / 2 + fontHeight / 4;

        fontMetricsInt.ascent = -bottom;
        fontMetricsInt.top = -bottom;
        fontMetricsInt.bottom = top;
        fontMetricsInt.descent = top;
    }
    return rect.right;
}

@Override
public void draw(Canvas canvas, CharSequence text, int start, int end,
                 float x, int top, int y, int bottom, Paint paint) {
    Drawable drawable = getDrawable();
    canvas.save();
    int transY = 0;
    transY = ((bottom - top) - drawable.getBounds().bottom) / 2 + top;
    canvas.translate(x, transY);
    drawable.draw(canvas);
    canvas.restore();
}
 

    7.imagespan 中内容超出屏幕宽度,会出现2个重复span

       str 为imagespan 内容字符

       screenWidth  为屏幕宽度

       使用TextUtils.ellipsize()方法格式化 内容部分 超出屏幕部分以...替换  

TextUtils.ellipsize(str, getPaint(),screenWidth, TextUtils.TruncateAt.END).toString()

以下为关键代码片段


    1. 添加一个span (isClick 在点击切换背景时使用)

public void insertSpan(final String str, boolean isClick){
    getText().append(str);
    SpannableStringBuilder spannableString=new SpannableStringBuilder(getText());

    View spanView = getSpanView(getContext(), str,isClick);
    BitmapDrawable bitmpaDrawable = (BitmapDrawable) UIUtils.convertViewToDrawable(spanView);
    bitmpaDrawable.setBounds(0, 0, bitmpaDrawable.getIntrinsicWidth(), bitmpaDrawable.getIntrinsicHeight());
    ClickableImageSpan imageSpan= new ClickableImageSpan(bitmpaDrawable);
    int len=getText().toString().length();
    int end=len;
    int start=len-str.length();
    imageSpan.setText(str);
    imageSpan.setStart(start);
    imageSpan.setEnd(end);
    spannableString.setSpan(imageSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

    ClickableSpan clickableSpan=new ClickableSpan() {
        @Override
        public void onClick(View widget) {
            judgeLastTextIsEmail(str);
        }
    };
    spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

    setText(spannableString);
    setSelection(spannableString.length());

}

public View getSpanView(Context context,String text,boolean isClick){
    final TextView view = new TextView(context);
    FrameLayout.LayoutParams params=new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,FrameLayout.LayoutParams.WRAP_CONTENT);
    view.setLayoutParams(params);
    view.setPadding(UIUtils.dip2px(context, 10), 0, UIUtils.dip2px(context, 10), 0);
    view.setText(text);
    view.setSingleLine(true);
    view.setTextSize(getTextSize());
    if(isClick){
        view.setBackgroundResource(R.drawable.edittext_span_back);
    }else{
        view.setBackgroundResource(R.drawable.edittext_span_non_back);
    }
    view.setTextColor(getCurrentTextColor());

    FrameLayout frameLayout=new FrameLayout(context);
    ViewGroup.LayoutParams frameParams=new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
    frameLayout.setLayoutParams(frameParams);
    frameLayout.addView(view);


    return frameLayout;
}


public class UIUtils {
   private final static int UPPER_LEFT_X = 0;
   private final static int UPPER_LEFT_Y = 0;

   public static Drawable convertViewToDrawable(View view) {
      int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
      view.measure(spec, spec);
      view.layout(UPPER_LEFT_X, UPPER_LEFT_Y, view.getMeasuredWidth(), view.getMeasuredHeight());
      Bitmap b = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
      Canvas c = new Canvas(b);
      c.translate(-view.getScrollX(), -view.getScrollY());
      view.draw(c);
      view.setDrawingCacheEnabled(true);
      Bitmap cacheBmp = view.getDrawingCache();
      Bitmap viewBmp = cacheBmp.copy(Bitmap.Config.ARGB_8888, true);
      cacheBmp.recycle();
      view.destroyDrawingCache();
      return new BitmapDrawable(viewBmp);
   }

   public static int dip2px(Context context,int dip) {
      final float scale = context.getResources().getDisplayMetrics().density;
      return (int) (dip * scale + 0.5f);
   }

   public static int px2sp(Context context,float pxValue) {
      final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
      return (int) (pxValue / fontScale + 0.5f);
   }

   public static int sp2px(Context context,float spValue) {
      final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
      return (int) (spValue * fontScale + 0.5f);
   }

   /**
    * hide soft keyboard
    */
   public static void hideSoftKeyboard(Activity activity, View view) {
      InputMethodManager imm = (InputMethodManager) activity
            .getSystemService(Activity.INPUT_METHOD_SERVICE);
      imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
   }

   /**
    * show soft keyboard
    */
   public static void showSoftKeyboard(Activity activity, View view) {
      InputMethodManager imm = (InputMethodManager) activity
            .getSystemService(Activity.INPUT_METHOD_SERVICE);
      imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
      imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);

   }
      
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值