溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

android中怎么實現長按選擇文字功能

發(fā)布時間:2021-06-29 14:47:03 來源:億速云 閱讀:226 作者:Leah 欄目:移動開發(fā)

android中怎么實現長按選擇文字功能,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

1.實現原理

原理其實也不難,簡單總結就是:繪制文字時把顯示的文字的坐標記錄下來(記錄文字的左上右上左下右下四個點坐標),作用就是為了計算滑動范圍。執(zhí)行了長按事件后,通過按的坐標,在當前顯示的文字數據中根據點的坐標查找到按著的字,得到長按后選擇的位置與文字。當執(zhí)行滑動選擇時,根據手指滑動的位置坐標與當前顯示的文字數據匹配來確定選擇的范圍與文字。

2.具體實現

a.封裝

為了便于操作,首先對顯示可見的字符、顯示的行數據進行封裝。

ShowChar:

public class ShowChar {//可見字符數據封裝

  public char chardata ;//字符數據
  public Boolean Selected =false;//當前字符是否被選中
  public Point TopLeftPosition = null;
  public Point TopRightPosition = null;
  public Point BottomLeftPosition = null;
  public Point BottomRightPosition = null;

  public float charWidth = 0;//字符寬度
  public int Index = 0;//當前字符位置


}

ShowLine :

public class ShowLine {//顯示的行數據
  public List<ShowChar> CharsData = null;

  /**
   *@return
   *--------------------
   *TODO 獲取該行的數據
   *--------------------
   */
  public String getLineData(){
    String linedata = "";  
    if(CharsData==null||CharsData.size()==0) return linedata;
    for(ShowChar c:CharsData){
      linedata = linedata+c.chardata;
    }
    return linedata;
  }
}

說明:閱讀器顯示數據是一行一行的,每行都有不確定數量的字符,每個字符有自己的信息,比如字符寬度、字符在數據集合中的下標等。繪制時,通過繪制ShowLine 去繪制每行的數據。

b.數據轉化

繪制前,我們需要先要把數據轉化為上面封裝的格式數據以便我們使用。這個要怎么做?因為我們需要將字符串轉化為一行一行的數據,同時每個字符的字符寬度需要測量出來。如果對繪制比較熟悉的話,應該會知道系統有個paint.measureText可以用來測量字符的寬度,這里可以借助這個來實現測量字符的寬度,同時轉化為我們想要行數據。

首先,寫個方法,可以將傳入的字符串轉化為行數據:

  /**
   *@param cs 
   *@param medsurewidth 行測量的最大寬度
   *@param textpadding 字符間距
   *@param paint 測量的畫筆
   *@return 如果cs為空或者長度為0,返回null
   *--------------------
   *TODO 
   *--------------------
   */
  public static BreakResult BreakText(char[] cs, float medsurewidth, float textpadding, Paint paint) {  
    if(cs==null||cs.length==0){return null;}
    BreakResult breakResult = new BreakResult();    
    breakResult.showChars = new ArrayList<ShowChar>();
    float width = 0;

    for (int i = 0, size = cs.length; i < size; i++) {
      String mesasrustr = String.valueOf(cs[i]);
      float charwidth = paint.measureText(mesasrustr);

      if (width <= medsurewidth && (width + textpadding + charwidth) > medsurewidth) {
        breakResult.ChartNums = i;
        breakResult.IsFullLine = true;
        return breakResult;
      }

      ShowChar showChar = new ShowChar();
      showChar.chardata = cs[i];
      showChar.charWidth = charwidth;     
      breakResult.showChars.add(showChar);
      width += charwidth + textpadding;
    }

    breakResult.ChartNums = cs.length;
    return breakResult;
  }



public static BreakResult BreakText(String text, float medsurewidth, float textpadding, Paint paint) {
    if (TextUtils.isEmpty(text)) {
      int[] is = new int[2];
      is[0] = 0;
      is[1] = 0;
      return null;
    }
    return BreakText(text.toCharArray(), medsurewidth, textpadding, paint);

  }

說明: BreakResult 是對測量結果的簡單封裝:

public class BreakResult {

  public int ChartNums = 0;//測量了的字符數
  public Boolean IsFullLine = false;//是否滿一行了
  public List<ShowChar> showChars = null;//測量了的字符數據

  public Boolean HasData() {
    return showChars != null && showChars.size() > 0;
  }
}

完成了上面的工作后,我們可以實現將我們顯示的數據轉化為需要的數據了。

下面是我們測試顯示的字符串:

String TextData = "jEh話說天下大勢,分久必合,合久必分。周末七國分爭,并入于秦。及秦滅之后,楚、漢分爭,又并入于漢。漢朝自高祖斬白蛇而起義,一統天下,后來光武中興,傳至獻帝,遂分為三國。推其致亂之由,殆始于桓、靈二帝?;傅劢d善類,崇信宦官。及桓帝崩,靈帝即位,大將軍竇武、太傅陳蕃共相輔佐。時有宦官曹節(jié)等弄權,竇武、陳蕃謀誅之,機事不密,反為所害,中涓自此愈橫"
      +

  "建寧二年四月望日,帝御溫德殿。方升座,殿角狂風驟起。只見一條大青蛇,從梁上飛將下來,蟠于椅上。帝驚倒,左右急救入宮,百官俱奔避。須臾,蛇不見了。忽然大雷大雨,加以冰雹,落到半夜方止,壞卻房屋無數。建寧四年二月,洛陽地震;又海水泛溢,沿海居民,盡被大浪卷入海中。光和元年,雌雞化雄。六月朔,黑氣十余丈,飛入溫德殿中。秋七月,有虹現于玉堂;五原山岸,盡皆崩裂。種種不祥,非止一端。帝下詔問群臣以災異之由,議郎蔡邕上疏,以為墮雞化,乃婦寺干政之所致,言頗切直。帝覽奏嘆息,因起更衣。曹節(jié)在后竊視,悉宣告左右;遂以他事陷邕于罪,放歸田里。后張讓、趙忠、封、段、曹節(jié)、侯覽、蹇碩、程曠、夏惲、郭勝十人朋比為奸,號為“十常侍”。帝尊信張讓,呼為“阿父”。朝政日非,以致天下人心思亂,盜賊蜂起。";

我們需要將這段字符串轉化為行數據,在初始化數據的操作,下面是初始化數據的方法initData:

List<ShowLine> mLinseData = null;

  private void initData(int viewwidth, int viewheight) {
    if (mLinseData == null) {
      //將數據轉化為行數據
      mLinseData = BreakText(viewwidth, viewheight);
    }

  }

  private List<ShowLine> BreakText(int viewwidth, int viewheight) {
    List<ShowLine> showLines = new ArrayList<ShowLine>();
    while (TextData.length() > 0) {
      BreakResult breakResult = TextBreakUtil.BreakText(TextData, viewwidth, 0, mPaint);

      if (breakResult != null && breakResult.HasData()) {
        ShowLine showLine = new ShowLine();
        showLine.CharsData = breakResult.showChars;
        showLines.add(showLine);

      } else {
        break;
      }

      TextData = TextData.substring(breakResult.ChartNums);

    }

    int index = 0;
    for (ShowLine l : showLines) {
      for (ShowChar c : l.CharsData) {
        c.Index = index++;
      }
    }
    return showLines;
  }

只要調用initData方法,我們就可以將TextData的數據轉為顯示的行數據Linedata集合mLinseData 。

值得注意的是,調用這個方法需求知道控件的長寬,根據view的生命周期,我們可以在onmeasures里面調用這個方法進行初始化。

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int viewwidth = getMeasuredWidth();
    int viewheight = getMeasuredHeight();
    initData(viewwidth, viewheight);
  }

數據轉化完成后,接著我們需要把數據一行一行的繪制出來:

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);  

    LineYPosition = TextHeight + LinePadding;//第一行顯示的y坐標
    for (ShowLine line : mLinseData) {
      DrawLineText(line, canvas);//繪制每一行,并記錄每個字符的坐標
    }
  }

DrawLineText方法:

private void DrawLineText(ShowLine line, Canvas canvas) {
    canvas.drawText(line.getLineData(), 0, LineYPosition, mPaint);

    float leftposition = 0;
    float rightposition = 0;
    float bottomposition = LineYPosition + mPaint.getFontMetrics().descent;

    for (ShowChar c : line.CharsData) {
      rightposition = leftposition + c.charWidth;
      Point tlp = new Point();
      c.TopLeftPosition = tlp;
      tlp.x = (int) leftposition;
      tlp.y = (int) (bottomposition - TextHeight);

      Point blp = new Point();
      c.BottomLeftPosition = blp;
      blp.x = (int) leftposition;
      blp.y = (int) bottomposition;

      Point trp = new Point();
      c.TopRightPosition = trp;
      trp.x = (int) rightposition;
      trp.y = (int) (bottomposition - TextHeight);

      Point brp = new Point();
      c.BottomRightPosition = brp;
      brp.x = (int) rightposition;
      brp.y = (int) bottomposition;
      leftposition = rightposition;

    }
    LineYPosition = LineYPosition + TextHeight + LinePadding;
  }

運行一下,目前顯示效果如下:

android中怎么實現長按選擇文字功能

實現這些后,接下來需要實現長按選擇功能以及滑動選擇文字功能。如何實現長按呢,自己寫肯定可以,只是也太麻煩了,所以我們這里借助系統提供的長按事件就可以。我實現的思路是這樣的,首先先將事件處理模式分四種:

private enum Mode {

    Normal, //正常模式
    PressSelectText,//長按選中文字
    SelectMoveForward, //向前滑動選中文字
    SelectMoveBack//向后滑動選中文字
  }

在沒有做任何處理情況下是Normal模式,如果手勢發(fā)生了,Down事件觸發(fā),記錄當前Down的坐標,如果用戶一直按著,必然觸發(fā)長按事件,模式轉化為PressSelectText,通過記錄的Down的坐標,去數據集合中找到當前長按的字符,繪畫出選擇的文字的背景。

思路是這樣,那么就干吧。首先注冊長按事件,在初始化使注冊該事件。

private void init() {
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setTextSize(29);

    mTextSelectPaint = new Paint();
    mTextSelectPaint.setAntiAlias(true);
    mTextSelectPaint.setTextSize(19);
    mTextSelectPaint.setColor(TextSelectColor);

    mBorderPointPaint = new Paint();
    mBorderPointPaint.setAntiAlias(true);
    mBorderPointPaint.setTextSize(19);
    mBorderPointPaint.setColor(BorderPointColor);

    FontMetrics fontMetrics = mPaint.getFontMetrics();
    TextHeight = Math.abs(fontMetrics.ascent) + Math.abs(fontMetrics.descent);

    setOnLongClickListener(mLongClickListener);

  }
private OnLongClickListener mLongClickListener = new OnLongClickListener() {

    @Override
    public boolean onLongClick(View v) {

      if (mCurrentMode == Mode.Normal) {
        if (Down_X > 0 && Down_Y > 0) {// 說明還沒釋放,是長按事件
          mCurrentMode = Mode.PressSelectText;
          postInvalidate();//刷新
        }
      }
      return false;
    }
  };

這里 Down_X , Down_Y ; 初始化值都是-1,如果執(zhí)行了down事件后它們肯定大于0,如果執(zhí)行了Action_up事件,釋放設置值為-1,只是為了判斷使用而已。

然后onDraw中需要判斷一下并繪制選擇的文字了。

@Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    LineYPosition = TextHeight + LinePadding;//第一行的y坐標
    for (ShowLine line : mLinseData) {
      DrawLineText(line, canvas);//繪制每一
    }

    if (mCurrentMode != Mode.Normal) {
      DrawSelectText(canvas);//如果不是正常的話,繪制選擇
    }
  }
private void DrawSelectText(Canvas canvas) {
    if (mCurrentMode == Mode.PressSelectText) {
      DrawPressSelectText(canvas);//繪制長按選擇的字符
    } else if (mCurrentMode == Mode.SelectMoveForward) {//向前滑動選擇
      DrawMoveSelectText(canvas);//繪制滑動時選擇的文字背景
    } else if (mCurrentMode == Mode.SelectMoveBack) {//向后滑動選擇
      DrawMoveSelectText(canvas);//繪制滑動時選擇的文字背景
    }
  }

這時如果執(zhí)行了長按事件,mCurrentMode == Mode.PressSelectText,將執(zhí)行繪制長按選擇的字符。

 //繪制長按選中的數據
private void DrawPressSelectText(Canvas canvas) {
    //根據按的坐標檢測找到長按的字符
    ShowChar p = DetectPressShowChar(Down_X, Down_Y);

    if (p != null) {// 找到了選擇的字符
      FirstSelectShowChar = LastSelectShowChar = p;
      mSelectTextPath.reset();
      mSelectTextPath.moveTo(p.TopLeftPosition.x, p.TopLeftPosition.y);
      mSelectTextPath.lineTo(p.TopRightPosition.x, p.TopRightPosition.y);
      mSelectTextPath.lineTo(p.BottomRightPosition.x, p.BottomRightPosition.y);
      mSelectTextPath.lineTo(p.BottomLeftPosition.x, p.BottomLeftPosition.y);
      //繪制文字背景
      canvas.drawPath(mSelectTextPath, mTextSelectPaint);
      //繪制邊界的線與指示塊
      DrawBorderPoint(canvas);

    }
  }

檢測點擊點所在的字符方法:

  /**
   *@param down_X2
   *@param down_Y2
   *@return
   *--------------------
   *TODO 檢測獲取按壓坐標所在位置的字符,沒有的話返回null
   *--------------------
   */
  private ShowChar DetectPressShowChar(float down_X2, float down_Y2) {

    for (ShowLine l : mLinseData) {
      for (ShowChar c : l.CharsData) {
        if (down_Y2 > c.BottomLeftPosition.y) {
          break;// 說明是在下一行
        }
        if (down_X2 >= c.BottomLeftPosition.x && down_X2 <= c.BottomRightPosition.x) {
          return c;
        }

      }
    }

    return null;
  }

基本上長按事件操作都完成了,我們運行長按文字看看效果:

繪制了長按選擇的字符后,我們需要實現按著左右的指示塊進行左右或者上下滑動去選擇文字。為了便于操作,向上滑動與向下滑動都有限制滑動范圍,如下圖:

android中怎么實現長按選擇文字功能

藍色的區(qū)域是手指按著后觸發(fā)允許滑動。按著左邊的小藍色區(qū)域,mCurrentMode == Mode.SelectMoveForward,允許向上滑動選擇文字,就是手指滑動坐標滑動到黃色區(qū)域有效。按著右邊的小藍色區(qū)域,mCurrentMode == Mode.SelectMoveBack,允許向下滑動選擇文字,就是手指滑動到綠色區(qū)域有效。

選擇時,我們只會記錄兩個字符,就是選擇的文字的開始字符與結束字符:

private ShowChar FirstSelectShowChar = null;
private ShowChar LastSelectShowChar = null;

注意的是當長按選擇一個字符后:FirstSelectShowChar = LastSelectShowChar;

所以整個過程是:滑動時,如果按著左邊的藍色區(qū)域,將允許向前滑動,這時mCurrentMode == Mode.SelectMoveForward,向前滑動即在黃色區(qū)域滑動,這時就可以根據手指滑動坐標找到滑動后的FirstSelectShowChar ,然后刷新界面。向下滑動同理。

下面是代碼實現:

先在Action_Down里判斷是向下滑動還是向下滑動,如果都不是,重置,使長按選擇的文字恢復原樣。

case MotionEvent.ACTION_DOWN:
      Down_X = Tounch_X;
      Down_Y = Tounch_Y;

      if (mCurrentMode != Mode.Normal) {
        Boolean isTrySelectMove = CheckIfTrySelectMove(Down_X, Down_Y);

        if (!isTrySelectMove) {// 如果不是準備滑動選擇文字,轉變?yōu)檎DJ?,隱藏選擇框
          mCurrentMode = Mode.Normal;
          invalidate();
        }
      }

      break;

在滑動時判斷,如果是向上滑動,檢測獲取當前滑動時的FirstSelectShowChar ;如果是向下滑動,檢測獲取當前滑動時的LastSelectShowChar ,然后刷新界面。

case MotionEvent.ACTION_MOVE:
      if (mCurrentMode == Mode.SelectMoveForward) {
        if (CanMoveForward(event.getX(), event.getY())) {// 判斷是否是向上移動
          ShowChar firstselectchar = DetectPressShowChar(event.getX(), event.getY());//獲取當前滑動坐標的下的字符
          if (firstselectchar != null) {
            FirstSelectShowChar = firstselectchar;
            invalidate();
          } 

        }

      } else if (mCurrentMode == Mode.SelectMoveBack) {

        if (CanMoveBack(event.getX(), event.getY())) {// 判斷是否可以向下移動         
          ShowChar lastselectchar = DetectPressShowChar(event.getX(), event.getY());//獲取當前滑動坐標的下的字符
          if (lastselectchar != null) {
            LastSelectShowChar = lastselectchar;
            invalidate();
          } 

        } 
      }

      break;

判斷是否向上滑動方法:

private boolean CanMoveForward(float Tounchx, float Tounchy) {

    Path p = new Path();
    p.moveTo(LastSelectShowChar.TopRightPosition.x, LastSelectShowChar.TopRightPosition.y);
    p.lineTo(getWidth(), LastSelectShowChar.TopRightPosition.y);
    p.lineTo(getWidth(), 0);
    p.lineTo(0, 0);
    p.lineTo(0, LastSelectShowChar.BottomRightPosition.y);
    p.lineTo(LastSelectShowChar.BottomRightPosition.x, LastSelectShowChar.BottomRightPosition.y);
    p.lineTo(LastSelectShowChar.TopRightPosition.x, LastSelectShowChar.TopRightPosition.y);

    return computeRegion(p).contains((int) Tounchx, (int) Tounchy);
  }

判斷是否向下滑動:

private boolean CanMoveBack(float Tounchx, float Tounchy) {

    Path p = new Path();
    p.moveTo(FirstSelectShowChar.TopLeftPosition.x, FirstSelectShowChar.TopLeftPosition.y);
    p.lineTo(getWidth(), FirstSelectShowChar.TopLeftPosition.y);
    p.lineTo(getWidth(), getHeight());
    p.lineTo(0, getHeight());
    p.lineTo(0, FirstSelectShowChar.BottomLeftPosition.y);
    p.lineTo(FirstSelectShowChar.BottomLeftPosition.x, FirstSelectShowChar.BottomLeftPosition.y);
    p.lineTo(FirstSelectShowChar.TopLeftPosition.x, FirstSelectShowChar.TopLeftPosition.y);

    return computeRegion(p).contains((int) Tounchx, (int) Tounchy);
  }
private Region computeRegion(Path path) {
    Region region = new Region();
    RectF f = new RectF();
    path.computeBounds(f, true);
    region.setPath(path, new Region((int) f.left, (int) f.top, (int) f.right, (int) f.bottom));
    return region;
  }

手勢操作處理完成了,剩下的就是在ondraw時判斷到mCurrentMode == Mode.SelectMoveForward或者mCurrentMode == Mode.SelectMoveBack繪制出選擇的范圍背景。

private void DrawSelectText(Canvas canvas) {
    if (mCurrentMode == Mode.PressSelectText) {
      DrawPressSelectText(canvas);//繪制長按選擇的字符
    } else if (mCurrentMode == Mode.SelectMoveForward) {//向前滑動選擇
      DrawMoveSelectText(canvas);//繪制滑動時選擇的文字背景
    } else if (mCurrentMode == Mode.SelectMoveBack) {//向后滑動選擇
      DrawMoveSelectText(canvas);//繪制滑動時選擇的文字背景
    }
  }
private void DrawMoveSelectText(Canvas canvas) {
    if (FirstSelectShowChar == null || LastSelectShowChar == null)     return;
    GetSelectData();//獲取選擇字符的數據,轉化為選擇的行數據
    DrawSeletLines(canvas);//繪制選擇的行數據
    DrawBorderPoint(canvas);//繪制出邊界的方塊或圓點
  }
private void DrawSeletLines(Canvas canvas) 
    DrawOaleSeletLinesBg(canvas);
  }

  private void DrawOaleSeletLinesBg(Canvas canvas) {// 繪制橢圓型的選中背景
    for (ShowLine l : mSelectLines) {      
      if (l.CharsData != null && l.CharsData.size() > 0) {        
        ShowChar fistchar = l.CharsData.get(0);
        ShowChar lastchar = l.CharsData.get(l.CharsData.size() - 1);

        float fw = fistchar.charWidth;
        float lw = lastchar.charWidth;

        RectF rect = new RectF(fistchar.TopLeftPosition.x, fistchar.TopLeftPosition.y,
            lastchar.TopRightPosition.x, lastchar.BottomRightPosition.y);

        canvas.drawRoundRect(rect, fw / 2,
             TextHeight / 2, mTextSelectPaint);

      }
    }
  }

關于android中怎么實現長按選擇文字功能問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業(yè)資訊頻道了解更多相關知識。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI