diff --git a/app/src/main/java/com/borjabravo/readmoretextviewsample/MainActivity.java b/app/src/main/java/com/borjabravo/readmoretextviewsample/MainActivity.java index 8f50499..b137353 100644 --- a/app/src/main/java/com/borjabravo/readmoretextviewsample/MainActivity.java +++ b/app/src/main/java/com/borjabravo/readmoretextviewsample/MainActivity.java @@ -1,11 +1,26 @@ package com.borjabravo.readmoretextviewsample; +import android.content.Context; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import android.widget.TextView; +import android.widget.Toast; + +import com.borjabravo.readmoretextview.ReadMoreTextView; + +import java.util.ArrayList; +import java.util.List; public class MainActivity extends AppCompatActivity { + private static final int COUNT = 30; + private List mItems = new ArrayList<>(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -18,5 +33,78 @@ protected void onCreate(Bundle savedInstanceState) { text3.setText(getString(R.string.lorem_ipsum3)); TextView text4 = findViewById(R.id.text4); text4.setText(getString(R.string.one_line_text)); + RecyclerView listView = (RecyclerView) findViewById(R.id.list); + listView.setLayoutManager(new LinearLayoutManager(this)); + initMockData(); + listView.setAdapter(new ItemAdapter(mItems)); + } + + private void initMockData() { + for (int i = 0; i < COUNT; i++) { + Item item = new Item(); + item.text = i + " : " + getString(R.string.lorem_ipsum); + item.readMore = true; + mItems.add(item); + } + } + + static class ItemAdapter extends RecyclerView.Adapter { + + private List mItems; + + public ItemAdapter(List items) { + mItems = new ArrayList<>(items); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, final int position) { + final Context context = holder.itemView.getContext(); + holder.text.setText(mItems.get(position).text); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(context, "you clicked " + position, Toast.LENGTH_SHORT).show(); + } + }); + + holder.text.setToggleWatcher(new ReadMoreTextView.Watcher() { + @Override + public void onExpanded() { + mItems.get(position).readMore = false; + } + + @Override + public void onCollapsed() { + mItems.get(position).readMore = true; + } + }); + + holder.text.toggleCollapsed(mItems.get(position).readMore); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + } + + static class ViewHolder extends RecyclerView.ViewHolder { + ReadMoreTextView text; + + public ViewHolder(View itemView) { + super(itemView); + text = (ReadMoreTextView) itemView.findViewById(R.id.text); + } + } + + static class Item { + public String text; + public boolean readMore; } } diff --git a/app/src/main/res/drawable/round_btn_bg.xml b/app/src/main/res/drawable/round_btn_bg.xml new file mode 100644 index 0000000..61f675c --- /dev/null +++ b/app/src/main/res/drawable/round_btn_bg.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 01a64c6..8b17d4b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -37,4 +37,10 @@ android:layout_height="wrap_content" /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item.xml b/app/src/main/res/layout/list_item.xml new file mode 100644 index 0000000..58ff126 --- /dev/null +++ b/app/src/main/res/layout/list_item.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/readmoretextview/src/main/java/com/borjabravo/readmoretextview/ReadMoreTextView.java b/readmoretextview/src/main/java/com/borjabravo/readmoretextview/ReadMoreTextView.java index f560151..f5bdb3a 100644 --- a/readmoretextview/src/main/java/com/borjabravo/readmoretextview/ReadMoreTextView.java +++ b/readmoretextview/src/main/java/com/borjabravo/readmoretextview/ReadMoreTextView.java @@ -18,14 +18,16 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; -import android.os.Build; import android.support.v4.content.ContextCompat; +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; -import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.widget.TextView; @@ -54,6 +56,8 @@ public class ReadMoreTextView extends TextView { private int lineEndIndex; private int trimLines; + private Watcher watcher; + public ReadMoreTextView(Context context) { this(context, null); } @@ -76,13 +80,56 @@ public ReadMoreTextView(Context context, AttributeSet attrs) { this.trimMode = typedArray.getInt(R.styleable.ReadMoreTextView_trimMode, TRIM_MODE_LINES); typedArray.recycle(); viewMoreSpan = new ReadMoreClickableSpan(); + setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + TextView widget = (TextView) v; + Spannable buffer = Spannable.Factory.getInstance().newSpannable(widget.getText()); + int action = event.getAction(); + + // https://stackoverflow.com/questions/8558732/listview-textview-with-linkmovementmethod-makes-list-item-unclickable + + // to fix a bug when call setMovementMethod will make list item unclickable + if (action == MotionEvent.ACTION_UP || + action == MotionEvent.ACTION_DOWN) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + + x += widget.getScrollX(); + y += widget.getScrollY(); + + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + + ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + + if (link.length != 0) { + if (action == MotionEvent.ACTION_UP) { + link[0].onClick(widget); + } else if (action == MotionEvent.ACTION_DOWN) { + Selection.setSelection(buffer, + buffer.getSpanStart(link[0]), + buffer.getSpanEnd(link[0])); + } + + return true; + } else { + Selection.removeSelection(buffer); + } + } + return false; + } + }); onGlobalLayoutLineEndIndex(); setText(); } private void setText() { super.setText(getDisplayableText(), bufferType); - setMovementMethod(LinkMovementMethod.getInstance()); setHighlightColor(Color.TRANSPARENT); } @@ -110,9 +157,7 @@ private CharSequence getTrimmedText(CharSequence text) { if (trimMode == TRIM_MODE_LINES) { if (text != null && lineEndIndex > 0) { if (readMore) { - if (getLayout().getLineCount() > trimLines) { - return updateCollapsedText(); - } + return updateCollapsedText(); } else { return updateExpandedText(); } @@ -122,10 +167,21 @@ private CharSequence getTrimmedText(CharSequence text) { } private CharSequence updateCollapsedText() { + if (watcher != null) { + watcher.onCollapsed(); + } int trimEndIndex = text.length(); switch (trimMode) { case TRIM_MODE_LINES: - trimEndIndex = lineEndIndex - (ELLIPSIZE.length() + trimCollapsedText.length() + 1); + trimEndIndex = lineEndIndex; + //find enough space to layout ELLIPSIZE + float ellipsizeWidth = getPaint().measureText(ELLIPSIZE + trimCollapsedText); + float collapsedWidth = getPaint().measureText(text.subSequence(trimEndIndex, trimEndIndex + 1).toString()); + while (collapsedWidth < ellipsizeWidth) { + --trimEndIndex; + collapsedWidth += getPaint().measureText(text.subSequence(trimEndIndex, trimEndIndex + 1).toString()); + } + if (trimEndIndex < 0) { trimEndIndex = trimLength + 1; } @@ -141,6 +197,9 @@ private CharSequence updateCollapsedText() { } private CharSequence updateExpandedText() { + if (watcher != null) { + watcher.onExpanded(); + } if (showTrimExpandedText) { SpannableStringBuilder s = new SpannableStringBuilder(text, 0, text.length()).append(trimExpandedText); return addClickableSpan(s, trimExpandedText); @@ -153,6 +212,15 @@ private CharSequence addClickableSpan(SpannableStringBuilder s, CharSequence tri return s; } + public void toggleCollapsed(boolean readMore) { + this.readMore = readMore; + setText(); + } + + public void setToggleWatcher(Watcher watcher) { + this.watcher = watcher; + } + public void setTrimLength(int trimLength) { this.trimLength = trimLength; setText(); @@ -193,28 +261,31 @@ public void updateDrawState(TextPaint ds) { private void onGlobalLayoutLineEndIndex() { if (trimMode == TRIM_MODE_LINES) { - getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override - public void onGlobalLayout() { + public boolean onPreDraw() { ViewTreeObserver obs = getViewTreeObserver(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - obs.removeOnGlobalLayoutListener(this); - } else { - obs.removeGlobalOnLayoutListener(this); - } + obs.removeOnPreDrawListener(this); refreshLineEndIndex(); setText(); + return true; } }); } } + @Override + public boolean performLongClick() { + //a side affect that every slide move will trigger a long click event + return false; + } + private void refreshLineEndIndex() { try { if (trimLines == 0) { - lineEndIndex = getLayout().getLineEnd(0); + lineEndIndex = getLayout().getLineVisibleEnd(0); } else if (trimLines > 0 && getLineCount() >= trimLines) { - lineEndIndex = getLayout().getLineEnd(trimLines - 1); + lineEndIndex = getLayout().getLineVisibleEnd(trimLines - 1); } else { lineEndIndex = INVALID_END_INDEX; } @@ -222,4 +293,10 @@ private void refreshLineEndIndex() { e.printStackTrace(); } } -} + + public interface Watcher { + public void onExpanded(); + + public void onCollapsed(); + } +} \ No newline at end of file