Project address: github.com/razerdp/Fri…

The last link: http://www.jianshu.com/p/1f85d3978bb5

The next link: http://www.jianshu.com/p/26dd3aad965a

Finally entered the custom control piece, do not know if there is anyone excited, anyway, next very excited. -V-


This article will implement a relatively simple control: click to expand the full text HMM… This is how it looks:

This east east estimate is the whole project in the most simple a control, of course, there are many examples on the Internet, the implementation is similar, this is the same implementation method.


Before we start, let’s think about how to implement this control. So far, I’ve come up with two solutions:

  • Inherit the TextView and determine if there is a full text by the number of lines. If there is, create a new line in the original text, concatenate the original text with SpannableStringBuilder +\n+ ClickableSpan containing the click event, and dynamically change the maxLines
  • The LinearLayout contains 2 TextViews, one for displaying and one for clicking. Determine whether there is a full text by the number of rows. If there is, the textView used for clicking is set to Visible, otherwise set to gone. Click the event to dynamically change the maxLines of the TextView used for display

After testing, the implementation of the first method is difficult, mainly because of the problem of creating another line, because maxLines had been set at the original presentation. If you want to create another line, it means adding maxLines, which results in the original text being displayed, and then the full text. Therefore, plan 2 is adopted.


That’s it. Let’s get to work

It is my custom to define attrs first, since I can design a rough prototype from defining attributes.

<declare-styleable name="ClickShowMoreLayout">
      <attr name="show_line" format="integer"/>
      <attr name="click_text" format="string"/>
      <attr name="text_color" format="color"/>
      <attr name="text_size" format="dimension"/>
  </declare-styleable>
Copy the code

We define four attributes: maximum number of lines to display, click on the expanded TextView text, show the color of the text, and show the size of the text.

And then there’s a whole bunch of stuff in the constructor, so I’m not going to post any code here, but I’m going to draw a picture

The initView method initializes two TextViews:

 private void initView(Context context) {
        mTextView=new TextView(context);
        mClickToShow=new TextView(context);

        mTextView.setTextSize(textSize);
        mTextView.setTextColor(textColor);
        mTextView.setMaxLines(showLine);

        mClickToShow.setTextSize(textSize);
        mClickToShow.setTextColor(getResources().getColor(R.color.nick));
        mClickToShow.setText(clickText);

        LinearLayout.LayoutParams params= new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        params.topMargin= UIHelper.dipToPx(context,10f);
        mClickToShow.setLayoutParams(params);
        mClickToShow.setOnClickListener(this);

        setOrientation(VERTICAL);
        addView(mTextView);
        addView(mClickToShow);
    }
Copy the code

It’s important to note that our LayoutParams can’t be retrieved by getLayoutParams — that’s empty.

The highlight is the method for setting text and the onClick method:

 public void setText(String str){
        mTextView.setText(str);
        mTextView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw(a) {
                if(! hasGetLineCount) { hasMore = mTextView.getLineCount() > showLine; hasGetLineCount=true; } mClickToShow.setVisibility(hasMore? VISIBLE:GONE); mTextView.getViewTreeObserver().removeOnPreDrawListener(this);
                return true; }}); }@Override
public void onClick(View v) {
        if (((TextView)v).getText().toString().equals(clickText)){
            mTextView.setMaxLines(Integer.MAX_VALUE);
            mClickToShow.setText("Fold");
        }else{ mTextView.setMaxLines(showLine); mClickToShow.setText(clickText); }}Copy the code

To determine if there’s more content, we can only get the total number of lines of text when the TextView is drawn, so the text hasn’t been drawn yet, so how do we get the total number of lines, we can’t get it in onDraw, so we’ll just get it in onPreDraw, and we’ll talk about that later. Take the total number of rows and compare it with our maximum number of rows to see if there are more and then set the visibility of the TextView and you’re done.

This section is complete, the next section will implement the “like” display control.


The following involves official source code, a little boring, can be skipped.

For onPreDraw(), the documentation says:

Callback method to be invoked when the view tree is about to be drawn. At this point, all views in the tree have been measured and given a frame. Clients can use this to adjust their scroll bounds or even to request a new layout before drawing occurs.

(Roughly meaning: The view tree will call this method before onDraw, when the view has been measured. Before draw, we can adjust the sliding boundary or even redeploy it.

TextView deployment of text is actually related to two layouts, one is StaticLayout, One is DynamicLayout. The difference between DynamicLayout and DynamicLayout is simply whether a setText uses a normal string or a set of characters with a span (there is also BoringLayout for single-line strings). [Please refer to Google TextView rendering mechanism for details]

. And since textview implements ViewTreeObserver OnPreDrawListener this interface, then the textview must implement the correction, this method, and there is certainly got StaticLayout or DynamicLayout, Otherwise we’re only going to get a linecount of 0.

So we’re going to find out if StaticLayout is implemented

We can look at TextView source:

Sumelayout is where we can find that

makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,physicalWidth, false);
Copy the code

Whatever the other parameters are, we’re going to find the target and we’re going to continue to find the method in this method

As you can see, dynamicLayout is used if a span is used, BoringLayout is used if it is singLeline, and StaticlayOut is used if neither is used (i.e. result==null)

Let’s continue to look at the methods in build:

 public StaticLayout build(a) {
            StaticLayout result = new StaticLayout(this);
            Builder.recycle(this);
            return result;
        }
Copy the code

Layout getLineCount is an abstract method, so StaticLayout is the only one that can implement this method. After a long search, it is found that out has a count of mLineCount in its private method

In fact, at this point, we can take a bold guess that the control or other elements are placed one line at a time, if more than, another line until the end. Of course, this is just a guess, not a check.