国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

android閱讀器長按選擇文字功能實(shí)現(xiàn)代碼

2019-12-12 02:32:06
字體:
供稿:網(wǎng)友

前言: 有時候我們需要實(shí)現(xiàn)長按選擇文字功能,比如閱讀器一般都有這個功能,有時候某個自定義控件上可能就有這種需求,如何實(shí)現(xiàn)呢?正好最近還算閑,想完善一下自己寫的那個輕量級的txt文件閱讀器(比如這個長按選擇文字的功能就想加進(jìn)去)。于是花了兩三天時間,實(shí)現(xiàn)了這個功能,效果還是不錯的。

首先先看看效果圖吧:

這里寫圖片描述

授人以魚不如授人以漁,下面具體實(shí)現(xiàn)原理的教程。

1.實(shí)現(xiàn)原理

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

2.具體實(shí)現(xiàn)

a.封裝

為了便于操作,首先對顯示可見的字符、顯示的行數(shù)據(jù)進(jìn)行封裝。

ShowChar:

public class ShowChar {//可見字符數(shù)據(jù)封裝  public char chardata ;//字符數(shù)據(jù)  public Boolean Selected =false;//當(dāng)前字符是否被選中  public Point TopLeftPosition = null;  public Point TopRightPosition = null;  public Point BottomLeftPosition = null;  public Point BottomRightPosition = null;  public float charWidth = 0;//字符寬度  public int Index = 0;//當(dāng)前字符位置}

ShowLine :

public class ShowLine {//顯示的行數(shù)據(jù)  public List<ShowChar> CharsData = null;  /**   *@return   *--------------------   *TODO 獲取該行的數(shù)據(jù)   *--------------------   */  public String getLineData(){    String linedata = "";      if(CharsData==null||CharsData.size()==0) return linedata;    for(ShowChar c:CharsData){      linedata = linedata+c.chardata;    }    return linedata;  }}

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

b.數(shù)據(jù)轉(zhuǎn)化

繪制前,我們需要先要把數(shù)據(jù)轉(zhuǎn)化為上面封裝的格式數(shù)據(jù)以便我們使用。這個要怎么做?因?yàn)槲覀冃枰獙⒆址D(zhuǎn)化為一行一行的數(shù)據(jù),同時每個字符的字符寬度需要測量出來。如果對繪制比較熟悉的話,應(yīng)該會知道系統(tǒng)有個paint.measureText可以用來測量字符的寬度,這里可以借助這個來實(shí)現(xiàn)測量字符的寬度,同時轉(zhuǎn)化為我們想要行數(shù)據(jù)。

首先,寫個方法,可以將傳入的字符串轉(zhuǎn)化為行數(shù)據(jù):

  /**   *@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 是對測量結(jié)果的簡單封裝:

public class BreakResult {  public int ChartNums = 0;//測量了的字符數(shù)  public Boolean IsFullLine = false;//是否滿一行了  public List<ShowChar> showChars = null;//測量了的字符數(shù)據(jù)  public Boolean HasData() {    return showChars != null && showChars.size() > 0;  }}

完成了上面的工作后,我們可以實(shí)現(xiàn)將我們顯示的數(shù)據(jù)轉(zhuǎn)化為需要的數(shù)據(jù)了。

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

String TextData = "jEh話說天下大勢,分久必合,合久必分。周末七國分爭,并入于秦。及秦滅之后,楚、漢分爭,又并入于漢。漢朝自高祖斬白蛇而起義,一統(tǒng)天下,后來光武中興,傳至獻(xiàn)帝,遂分為三國。推其致亂之由,殆始于桓、靈二帝。桓帝禁錮善類,崇信宦官。及桓帝崩,靈帝即位,大將軍竇武、太傅陳蕃共相輔佐。時有宦官曹節(jié)等弄權(quán),竇武、陳蕃謀誅之,機(jī)事不密,反為所害,中涓自此愈橫"      +  "建寧二年四月望日,帝御溫德殿。方升座,殿角狂風(fēng)驟起。只見一條大青蛇,從梁上飛將下來,蟠于椅上。帝驚倒,左右急救入宮,百官俱奔避。須臾,蛇不見了。忽然大雷大雨,加以冰雹,落到半夜方止,壞卻房屋無數(shù)。建寧四年二月,洛陽地震;又海水泛溢,沿海居民,盡被大浪卷入海中。光和元年,雌雞化雄。六月朔,黑氣十余丈,飛入溫德殿中。秋七月,有虹現(xiàn)于玉堂;五原山岸,盡皆崩裂。種種不祥,非止一端。帝下詔問群臣以災(zāi)異之由,議郎蔡邕上疏,以為墮雞化,乃婦寺干政之所致,言頗切直。帝覽奏嘆息,因起更衣。曹節(jié)在后竊視,悉宣告左右;遂以他事陷邕于罪,放歸田里。后張讓、趙忠、封、段、曹節(jié)、侯覽、蹇碩、程曠、夏惲、郭勝十人朋比為奸,號為“十常侍”。帝尊信張讓,呼為“阿父”。朝政日非,以致天下人心思亂,盜賊蜂起。";

我們需要將這段字符串轉(zhuǎn)化為行數(shù)據(jù),在初始化數(shù)據(jù)的操作,下面是初始化數(shù)據(jù)的方法initData:

List<ShowLine> mLinseData = null;  private void initData(int viewwidth, int viewheight) {    if (mLinseData == null) {      //將數(shù)據(jù)轉(zhuǎn)化為行數(shù)據(jù)      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;  }

只要調(diào)用initData方法,我們就可以將TextData的數(shù)據(jù)轉(zhuǎn)為顯示的行數(shù)據(jù)Linedata集合mLinseData 。

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

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

數(shù)據(jù)轉(zhuǎn)化完成后,接著我們需要把數(shù)據(jù)一行一行的繪制出來:

  @Override  protected void onDraw(Canvas canvas) {    super.onDraw(canvas);      LineYPosition = TextHeight + LinePadding;//第一行顯示的y坐標(biāo)    for (ShowLine line : mLinseData) {      DrawLineText(line, canvas);//繪制每一行,并記錄每個字符的坐標(biāo)    }  }

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;  }

運(yùn)行一下,目前顯示效果如下:

這里寫圖片描述

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

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

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

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

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事件,釋放設(shè)置值為-1,只是為了判斷使用而已。

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

@Override  protected void onDraw(Canvas canvas) {    super.onDraw(canvas);    LineYPosition = TextHeight + LinePadding;//第一行的y坐標(biāo)    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í)行繪制長按選擇的字符。

     //繪制長按選中的數(shù)據(jù)private void DrawPressSelectText(Canvas canvas) {    //根據(jù)按的坐標(biāo)檢測找到長按的字符    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);    }  }

檢測點(diǎn)擊點(diǎn)所在的字符方法:

  /**   *@param down_X2   *@param down_Y2   *@return   *--------------------   *TODO 檢測獲取按壓坐標(biāo)所在位置的字符,沒有的話返回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;  }

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

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

這里寫圖片描述

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

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

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

注意的是當(dāng)長按選擇一個字符后:FirstSelectShowChar = LastSelectShowChar;

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

下面是代碼實(shí)現(xiàn):

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

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) {// 如果不是準(zhǔn)備滑動選擇文字,轉(zhuǎn)變?yōu)檎DJ?,隱藏選擇框          mCurrentMode = Mode.Normal;          invalidate();        }      }      break;

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

case MotionEvent.ACTION_MOVE:      if (mCurrentMode == Mode.SelectMoveForward) {        if (CanMoveForward(event.getX(), event.getY())) {// 判斷是否是向上移動          ShowChar firstselectchar = DetectPressShowChar(event.getX(), event.getY());//獲取當(dāng)前滑動坐標(biāo)的下的字符          if (firstselectchar != null) {            FirstSelectShowChar = firstselectchar;            invalidate();          }         }      } else if (mCurrentMode == Mode.SelectMoveBack) {        if (CanMoveBack(event.getX(), event.getY())) {// 判斷是否可以向下移動                   ShowChar lastselectchar = DetectPressShowChar(event.getX(), event.getY());//獲取當(dāng)前滑動坐標(biāo)的下的字符          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();//獲取選擇字符的數(shù)據(jù),轉(zhuǎn)化為選擇的行數(shù)據(jù)    DrawSeletLines(canvas);//繪制選擇的行數(shù)據(jù)    DrawBorderPoint(canvas);//繪制出邊界的方塊或圓點(diǎn)  }
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);      }    }  }

基本完成了,運(yùn)行一下,效果還是不錯的。

這里寫圖片描述

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 东台市| 吴江市| 静乐县| 醴陵市| 江安县| 安康市| 栾川县| 金沙县| 泊头市| 肇源县| 黎城县| 集贤县| 巴彦淖尔市| 简阳市| 灵台县| 资兴市| 公主岭市| 乳山市| 东乡| 郑州市| 尉氏县| 商城县| 勐海县| 恭城| 黔南| 宁都县| 广饶县| 南阳市| 利川市| 南阳市| 如皋市| 永和县| 宜州市| 双流县| 津市市| 安多县| 北京市| 开封县| 建湖县| 静海县| 潞城市|