Customize QQ-like Main Interface Tab

Posted by jzhang1013 on Sat, 29 Jun 2019 18:20:25 +0200

Customize QQ Home Interface Tab

I took time to learn the blog of big sperm and downloaded it by the way, but it's really nauseous that CSDN does not have the function of one-click reload, so I'm bored!!!Of course, you can also go to his Xiaoo to learn Xiaoo. Click to jump to the original blog post

QQ Android version effect pasted first

You can see this cute tab, in fact, it's easy to get it out with the xml layout, but the blogger takes you all encapsulated into a custom control! (Look at the place where I darken, haha, the original blogger was single, although you go to sneak it)

Blogger effect

This speed.Sorry, bloggers don't know why it's so fast.

As you can see, there's plenty of support for packages as well, calculating widths based on the custom property tabWidht
In fact, it is very simple to achieve, the following bloggers with white people to achieve it, Daniel please ignore

Analysis

Question:

To achieve this effect, if we inherit View, then the text inside, the inner border, and the rounded corners will all be drawn by ourselves
It also supports font size changes and text alignment, which is obviously too expensive
In our usual xml layout, it's easy to think of linear layouts if we encounter similar effects
Then put several text controls inside and use weights to divide the width equally, so the idea for today's implementation is this, but usually we encapsulate the code in xml handwriting!

Overall ideas

1. Custom controls inherit LinearLayout and set themselves horizontally
2. Read custom attributes into classes
3. Add TextView controls to LinearLayout based on all custom properties
4. Finally, it's shown by the system (it's not our job at this point.)

Excess

1. Rounded corners are achieved by using the background and GradientDrawable is fully qualified
2. Add a TextView click event, change the selected subscript, and call the third step above!
3. Provide an interface for users to listen for selected subscripts and text

To be finished

First of all, international conventions determine the inherited parent

The three-parameter constructor is just a few of the steps we mentioned above

Supported properties and their default values

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="XTabHost">
        <!--Radius-->
        <attr name="radius" format="dimension" />
        <!--Text size-->
        <attr name="text_size" format="dimension" />
        <!--Text Selected Color-->
        <attr name="text_select_color" format="color" />
        <!--Text unselected color-->
        <attr name="text_unselect_color" format="color" />
        <!--tab selected color-->
        <attr name="tab_select_color" format="color" />
        <!--tab unselected color-->
        <attr name="tab_unselect_color" format="color" />
        <!--tab spacing-->
        <attr name="tab_space" format="dimension" />
        <!--tab width, used when wrapping-->
        <attr name="tab_width" format="dimension"/>
        <!--tab high, used when wrapping-->
        <attr name="tab_height" format="dimension"/>
        <!--The overall background-->
        <attr name="bg" format="color" />
        <!--Defau lt number of displays-->
        <attr name="default_index" format="integer" />
        <!--Displayed text array-->
        <attr name="src" format="reference" />
    </declare-styleable>
</resources>

Corresponds to a class

 /**
     * Background of your own controls
     */
    private int backBg = Color.WHITE;

    /**
     * No tab selected background
     */
    private int unSelectTabBg = Color.BLUE;

    /**
     * Background of selected tab
     */
    private int selectTabBg = Color.WHITE;

    /**
     * The width and height of a Tab, which is used when it is a package
     * -1 Indicates no effect, calculated as packaged children
     * 80 Is the unit of dp
     */
    private int tabWidth = 80, tabHeight = -1;

    /**
     * Color of unselected text
     */
    private int unSelectTextColor = Color.WHITE;

    /**
     * Color of selected text
     */
    private int selectTextColor = Color.BLUE;

    /**
     * Default font size, sp
     */
    private int textSize = 16;

    /**
     * Spacing, px
     */
    private int space = 1;

    /**
     * Rounded radius, dp
     */
    private int radius = 0;

    /**
     * Current Subscript
     */
    private int curIndex = 1;

    /**
     * All text to display
     */
    private String[] textArr = new String[]{};

You can see that the effect of these properties is basically used by bloggers in the rendering.

Read Custom Properties

 /**
     * Read Custom Properties
     *
     * @param context
     * @param attrs
     */
    private void readAttr(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.XTabHost);

        //Get custom properties

        curIndex = (int) a.getInt(R.styleable.XTabHost_default_index, 0);
        radius = a.getDimensionPixelSize(R.styleable.XTabHost_radius, dpToPx(radius));
        backBg = a.getColor(R.styleable.XTabHost_bg, Color.WHITE);
        unSelectTabBg = a.getColor(R.styleable.XTabHost_tab_unselect_color, 
        Color.parseColor("#51B5EF"));
        selectTabBg = a.getColor(R.styleable.XTabHost_tab_select_color, Color.WHITE);
        Color.WHITE);
        Color.parseColor("#51B5EF"));
        textSize = a.getDimensionPixelSize(R.styleable.XTabHost_text_size, 16);
        space = a.getDimensionPixelSize(R.styleable.XTabHost_tab_space, 1);
        tabWidth = a.getDimensionPixelSize(R.styleable.XTabHost_tab_width, 
        dpToPx(tabWidth));
        tabHeight = a.getDimensionPixelSize(R.styleable.XTabHost_tab_height, -1);

        CharSequence[] arr = a.getTextArray(R.styleable.XTabHost_src);
        if (arr != null) {
            String[] tArr = new String[arr.length];
            for (int i = 0; i < arr.length; i++) {
                tArr[i] = String.valueOf(arr[i]);
            }
            textArr = tArr;
        }

        a.recycle();
    }

Since each read property is commented on at the time it is defined above, it is not explained

Generate effects based on supported attributes

  /**
     * Make an effect based on all the parameters
     */
    private void sove() {

        GradientDrawable dd = new GradientDrawable();
        //Set rounded corners
        dd.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});
        //Set background color
        dd.setColor(backBg);
        //Compatible with low versions
        if (Build.VERSION.SDK_INT >= 16) {
            setBackground(dd);
        } else {
            setBackgroundDrawable(dd);
        }

        //Remove all children
        removeAllViews();

        if (curIndex >= textArr.length || curIndex < 0) {
            curIndex = 0;
        }

        for (int i = 0; i < textArr.length; i++) {

            //Create a text
            TextView tv = new TextView(getContext());

            //Create layout objects for text
            LayoutParams params = new LayoutParams(
                    0, ViewGroup.LayoutParams.MATCH_PARENT
            );

            if (i > 0) {
                params.leftMargin = space;
            }

            GradientDrawable d = getFitGradientDrawable(i);

            //Set the selected color and background if checked
            if (curIndex == i) {
                tv.setTextColor(selectTextColor);
                d.setColor(selectTabBg);
            } else {
                tv.setTextColor(unSelectTextColor);
                d.setColor(unSelectTabBg);
            }

            //Set Text
            tv.setText(textArr[i]);
            //Set text to appear in the middle
            tv.setGravity(Gravity.CENTER);
            //Set Text Size
            tv.setTextSize(textSize);
            //Set the background of the text, compatible with lower versions
            if (Build.VERSION.SDK_INT >= 16) {
                tv.setBackground(d);
            } else {
                //noinspection deprecation
                tv.setBackgroundDrawable(d);
            }

            //Set the weight of the text (tab)
            params.weight = 1;

            tv.setLayoutParams(params);

            tv.setTag(i);
            tv.setOnClickListener(this);

            //Add Children
            addView(tv);

        }

    }
  /**
     * Get the background of each tab, also known as TextView, with rounded corners at the far left
     * The rightmost side has a rounded effect on the right
     * All four corners have rounded corners on the left and right
     *
     * @param index tab subscripting of
     * @return
     */
    private GradientDrawable getFitGradientDrawable(int index) {
        GradientDrawable d = null;
        //Round corners based on Subscripts
        if (index == 0 && index == textArr.length - 1) {//If there is only one moment
            d = new GradientDrawable();
            //Set rounded corners
            d.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});
        } else if (index == 0) { //If it is the leftmost, the upper left corner and the lower left corner are rounded
            d = new GradientDrawable();
            //Set rounded corners
            d.setCornerRadii(new float[]{radius, radius, 0, 0, 0, 0, radius, radius});
        } else if (index == textArr.length - 1) {//If it is the rightmost corner, the upper right corner and the lower right corner are rounded
            d = new GradientDrawable();
            //Set rounded corners
            d.setCornerRadii(new float[]{0, 0, radius, radius, radius, radius, 0, 0});
        } else { //If in the middle, then there are no rounded corners
            d = new GradientDrawable();
            //Set rounded corners
            d.setCornerRadii(new float[]{0, 0, 0, 0, 0, 0, 0, 0});
        }
        return d;
    }

This code highlights

The first long method, sove, is the third step in our analysis implementation process above, achieving results based on all the properties
It's really simple, first remove all the children, then add N TextView s based on the number of arrays of words, so that each
A TextView is a parent container whose width is divided equally and whose height is filled equally as it was written in xml
During the addition process we need to determine if the current Tab has rounded corners, because we can see the effect?
There are rounded corners on the left and also on the right, so the method getFitGradientDrawable(int index);
This is to get the background of the specified subscript, which is actually to get the background that each TextView should use
Before the for loop starts, we can see that we also set our own background, which is in all four corners.
Then at the end of the code, add a click event for each TextView, then toggle the selected TextView and uncheck it
The TextView effect of

Remaining Code

@Override
    public void onClick(View v) {

        //Get Subscript
        int index = (int) v.getTag();

        //If the click is the same, do not process
        if (index == curIndex) {
            return;
        }

        //Get the current TextView
        TextView tv = (TextView) getChildAt(curIndex);
        //Set to unselected text and background
        tv.setTextColor(unSelectTextColor);
        GradientDrawable d = getFitGradientDrawable(curIndex);
        d.setColor(unSelectTabBg);

        if (Build.VERSION.SDK_INT >= 16) {
            tv.setBackground(d);
        } else {
            //noinspection deprecation
            tv.setBackgroundDrawable(d);
        }

        //Record selected subscript
        curIndex = index;

        //Get the currently selected TextView
        tv = (TextView) getChildAt(curIndex);
        //Set to Selected Text and Selected Background
        tv.setTextColor(selectTextColor);
        d = getFitGradientDrawable(curIndex);
        d.setColor(selectTabBg);
        if (Build.VERSION.SDK_INT >= 16) {
            tv.setBackground(d);
        } else {
            //noinspection deprecation
            tv.setBackgroundDrawable(d);
        }

        //Notify users if they listen
        if (mOnSelectListener != null) {
            mOnSelectListener.onSelect(index, textArr[index]);
        }

    }

    /**
     * dp Units converted to px's
     *
     * @param dps
     * @return
     */
    int dpToPx(int dps) {
        return Math.round(getResources().getDisplayMetrics().density * dps);
    }

    /**
     * sp To px
     *
     * @param spVal
     * @return
     */
    int spToPx(float spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());
    }

    private OnSelectListener mOnSelectListener;

    /**
     * Set up monitoring
     *
     * @param mOnSelectListener
     */
    public void setOnSelectListener(OnSelectListener mOnSelectListener) {
        this.mOnSelectListener = mOnSelectListener;
    }

    /**
     * Callback interface
     */
    public interface OnSelectListener {

        /**
         * callback
         *
         * @param index
         * @param text
         */
        void onSelect(int index, String text);

    }

Paste out all the code below

/**
 * Created by cxj on 2017/2/19.
 * Tabs that mimic the qq main interface
 */
public class XTabHost extends LinearLayout implements View.OnClickListener {


    public XTabHost(Context context) {
        this(context, null);
    }

    public XTabHost(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public XTabHost(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //Set child alignment to horizontal
        setOrientation(HORIZONTAL);

        //Read Custom Properties
        readAttr(context, attrs);

        //Display effect
        sove();

    }

    /**
     * Read Custom Properties
     *
     * @param context
     * @param attrs
     */
    private void readAttr(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.XTabHost);

        //Get custom properties

        curIndex = (int) a.getInt(R.styleable.XTabHost_default_index, 0);
        radius = a.getDimensionPixelSize(R.styleable.XTabHost_radius, dpToPx(radius));
        backBg = a.getColor(R.styleable.XTabHost_bg, Color.WHITE);
        unSelectTabBg = a.getColor(R.styleable.XTabHost_tab_unselect_color, Color.parseColor("#51B5EF"));
        selectTabBg = a.getColor(R.styleable.XTabHost_tab_select_color, Color.WHITE);
        unSelectTextColor = a.getColor(R.styleable.XTabHost_text_unselect_color, Color.WHITE);
        selectTextColor = a.getColor(R.styleable.XTabHost_text_select_color, Color.parseColor("#51B5EF"));
        textSize = a.getDimensionPixelSize(R.styleable.XTabHost_text_size, 16);
        space = a.getDimensionPixelSize(R.styleable.XTabHost_tab_space, 1);
        tabWidth = a.getDimensionPixelSize(R.styleable.XTabHost_tab_width, dpToPx(tabWidth));
        tabHeight = a.getDimensionPixelSize(R.styleable.XTabHost_tab_height, -1);

        CharSequence[] arr = a.getTextArray(R.styleable.XTabHost_src);
        if (arr != null) {
            String[] tArr = new String[arr.length];
            for (int i = 0; i < arr.length; i++) {
                tArr[i] = String.valueOf(arr[i]);
            }
            textArr = tArr;
        }

        a.recycle();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


        //Get Calculation Modes
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        //Get recommended width and height
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        if (modeWidth == MeasureSpec.EXACTLY) { //If yes

        } else { //If packaged or in a horizontal list
            if (tabWidth > -1) {
                for (int i = 0; i < getChildCount(); i++) {
                    TextView view = (TextView) getChildAt(i);
                    LayoutParams lp = (LayoutParams) view.getLayoutParams();
                    lp.width = tabWidth;
                }
            }
        }

        if (modeHeight == MeasureSpec.EXACTLY) { //If yes

        } else { //If packaged or in a vertical list
            if (tabHeight > -1) {
                for (int i = 0; i < getChildCount(); i++) {
                    TextView view = (TextView) getChildAt(i);
                    LayoutParams lp = (LayoutParams) view.getLayoutParams();
                    lp.height = tabHeight;
                }
            }

        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    }

    /**
     * Background of your own controls
     */
    private int backBg = Color.WHITE;

    /**
     * No tab selected background
     */
    private int unSelectTabBg = Color.BLUE;

    /**
     * Background of selected tab
     */
    private int selectTabBg = Color.WHITE;

    /**
     * The width and height of a Tab, which is used when it is a package
     * -1 Indicates no effect, calculated as packaged children
     * 80 Is the unit of dp
     */
    private int tabWidth = 80, tabHeight = -1;

    /**
     * Color of unselected text
     */
    private int unSelectTextColor = Color.WHITE;

    /**
     * Color of selected text
     */
    private int selectTextColor = Color.BLUE;

    /**
     * Default font size, sp
     */
    private int textSize = 16;

    /**
     * Spacing, px
     */
    private int space = 1;

    /**
     * Rounded radius, dp
     */
    private int radius = 0;

    /**
     * Current Subscript
     */
    private int curIndex = 1;

    /**
     * All text to display
     */
    private String[] textArr = new String[]{};


    /**
     * Make an effect based on all the parameters
     */
    private void sove() {

        GradientDrawable dd = new GradientDrawable();
        //Set rounded corners
        dd.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});
        //Set background color
        dd.setColor(backBg);
        //Compatible with low versions
        if (Build.VERSION.SDK_INT >= 16) {
            setBackground(dd);
        } else {
            setBackgroundDrawable(dd);
        }

        //Remove all children
        removeAllViews();

        if (curIndex >= textArr.length || curIndex < 0) {
            curIndex = 0;
        }

        for (int i = 0; i < textArr.length; i++) {

            //Create a text
            TextView tv = new TextView(getContext());

            //Create layout objects for text
            LayoutParams params = new LayoutParams(
                    0, ViewGroup.LayoutParams.MATCH_PARENT
            );

            if (i > 0) {
                params.leftMargin = space;
            }

            GradientDrawable d = getFitGradientDrawable(i);

            //Set the selected color and background if checked
            if (curIndex == i) {
                tv.setTextColor(selectTextColor);
                d.setColor(selectTabBg);
            } else {
                tv.setTextColor(unSelectTextColor);
                d.setColor(unSelectTabBg);
            }

            //Set Text
            tv.setText(textArr[i]);
            //Set text to appear in the middle
            tv.setGravity(Gravity.CENTER);
            //Set Text Size
            tv.setTextSize(textSize);
            //Set the background of the text, compatible with lower versions
            if (Build.VERSION.SDK_INT >= 16) {
                tv.setBackground(d);
            } else {
                //noinspection deprecation
                tv.setBackgroundDrawable(d);
            }

            //Set the weight of the text (tab)
            params.weight = 1;

            tv.setLayoutParams(params);

            tv.setTag(i);
            tv.setOnClickListener(this);

            //Add Children
            addView(tv);

        }

    }

    /**
     * Get a background image of each tab, with rounded corners at the left
     * The rightmost side has a rounded effect on the right
     * All four corners have rounded corners on the left and right
     *
     * @param index tab subscripting of
     * @return
     */
    private GradientDrawable getFitGradientDrawable(int index) {
        GradientDrawable d = null;
        //Round corners based on Subscripts
        if (index == 0 && index == textArr.length - 1) {
            d = new GradientDrawable();
            //Set rounded corners
            d.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});
        } else if (index == 0) {
            d = new GradientDrawable();
            //Set rounded corners
            d.setCornerRadii(new float[]{radius, radius, 0, 0, 0, 0, radius, radius});
        } else if (index == textArr.length - 1) {
            d = new GradientDrawable();
            //Set rounded corners
            d.setCornerRadii(new float[]{0, 0, radius, radius, radius, radius, 0, 0});
        } else {
            d = new GradientDrawable();
            //Set rounded corners
            d.setCornerRadii(new float[]{0, 0, 0, 0, 0, 0, 0, 0});
        }
        return d;
    }


    @Override
    public void onClick(View v) {

        //Get Subscript
        int index = (int) v.getTag();

        //If the click is the same, do not process
        if (index == curIndex) {
            return;
        }

        //Get the current TextView
        TextView tv = (TextView) getChildAt(curIndex);
        //Set to unselected text and background
        tv.setTextColor(unSelectTextColor);
        GradientDrawable d = getFitGradientDrawable(curIndex);
        d.setColor(unSelectTabBg);

        if (Build.VERSION.SDK_INT >= 16) {
            tv.setBackground(d);
        } else {
            //noinspection deprecation
            tv.setBackgroundDrawable(d);
        }

        //Record selected subscript
        curIndex = index;

        //Get the currently selected TextView
        tv = (TextView) getChildAt(curIndex);
        //Set to Selected Text and Selected Background
        tv.setTextColor(selectTextColor);
        d = getFitGradientDrawable(curIndex);
        d.setColor(selectTabBg);
        if (Build.VERSION.SDK_INT >= 16) {
            tv.setBackground(d);
        } else {
            //noinspection deprecation
            tv.setBackgroundDrawable(d);
        }

        //Notify users if they listen
        if (mOnSelectListener != null) {
            mOnSelectListener.onSelect(index, textArr[index]);
        }

    }

    /**
     * dp Units converted to px's
     *
     * @param dps
     * @return
     */
    int dpToPx(int dps) {
        return Math.round(getResources().getDisplayMetrics().density * dps);
    }

    /**
     * sp To px
     *
     * @param spVal
     * @return
     */
    int spToPx(float spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());
    }

    private OnSelectListener mOnSelectListener;

    /**
     * Set up monitoring
     *
     * @param mOnSelectListener
     */
    public void setOnSelectListener(OnSelectListener mOnSelectListener) {
        this.mOnSelectListener = mOnSelectListener;
    }

    /**
     * Callback interface
     */
    public interface OnSelectListener {

        /**
         * callback
         *
         * @param index
         * @param text
         */
        void onSelect(int index, String text);

    }

}

International practice Demo: Source Download

Learn, by the way, in reality, although xml can also be achieved, but this feeling of jiao tall aha-ha of course is mainly learning, and finally reload the record!!!En is excellent, and don't forget to introduce Feiha to me, even to the original Blogger

Topics: xml Android encoding