com.arksh.summer.ui.zone.widget.ExpandableTextView.java Source code

Java tutorial

Introduction

Here is the source code for com.arksh.summer.ui.zone.widget.ExpandableTextView.java

Source

/*
 * Copyright (C) 2011 The Android Open Source Project
 * Copyright 2014 Manabu Shimobe
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.arksh.summer.ui.zone.widget;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.SparseBooleanArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.arksh.common.utils.DisplayUtil;
import com.arksh.summer.R;

public class ExpandableTextView extends LinearLayout implements View.OnClickListener {

    /*  */
    private static final int MAX_COLLAPSED_LINES = 5;

    /*  */
    private static final int DEFAULT_ANIM_DURATION = 200;

    /*textview*/
    protected TextView mTvContent;

    /*textview*/
    protected TextView mTvExpandCollapse;

    /*??*/
    private boolean mRelayout;

    /**/
    private boolean mCollapsed = true;

    /**/
    private Drawable mExpandDrawable;
    /**/
    private Drawable mCollapseDrawable;
    /**/
    private int mAnimationDuration;
    /*?*/
    private boolean mAnimating;
    /* ? */
    private OnExpandStateChangeListener mListener;
    /* listview??item/? */
    private SparseBooleanArray mCollapsedStatus;
    /* ? */
    private int mPosition;

    /*??*/
    private int mMaxCollapsedLines;

    /*linerlayout*/
    private int mCollapsedHeight;

    /*tv?padding*/
    private int mTextHeightWithMaxLines;

    /*tvMarginTopAmndBottom*/
    private int mMarginBetweenTxtAndBottom;

    /**/
    private int contentTextColor;
    /**/
    private int collapseExpandTextColor;
    /*?*/
    private float contentTextSize;
    /*?*/
    private float collapseExpandTextSize;
    /**/
    private String textCollapse;
    /**/
    private String textExpand;
    //?
    private boolean showExpandCollapseDrawable = true;

    /*?*/
    private int grarity;

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

    public ExpandableTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public ExpandableTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    @Override
    public void setOrientation(int orientation) {
        if (LinearLayout.HORIZONTAL == orientation) {
            throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");
        }
        super.setOrientation(orientation);
    }

    /**
     * ?
     * @param attrs
     */
    private void init(AttributeSet attrs) {
        mCollapsedStatus = new SparseBooleanArray();

        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableTextView);
        mMaxCollapsedLines = typedArray.getInt(R.styleable.ExpandableTextView_maxCollapsedLines,
                MAX_COLLAPSED_LINES);
        mAnimationDuration = typedArray.getInt(R.styleable.ExpandableTextView_animDuration, DEFAULT_ANIM_DURATION);
        mExpandDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_expandDrawable);
        mCollapseDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_collapseDrawable);

        textCollapse = typedArray.getString(R.styleable.ExpandableTextView_textCollapse);
        textExpand = typedArray.getString(R.styleable.ExpandableTextView_textExpand);
        showExpandCollapseDrawable = typedArray
                .getBoolean(R.styleable.ExpandableTextView_showExpandCollapseDrawable, true);

        if (showExpandCollapseDrawable) {
            if (mExpandDrawable == null) {
                mExpandDrawable = ContextCompat.getDrawable(getContext(), R.drawable.icon_green_arrow_down);
            }
            if (mCollapseDrawable == null) {
                mCollapseDrawable = ContextCompat.getDrawable(getContext(), R.drawable.icon_green_arrow_up);
            }
        }

        if (TextUtils.isEmpty(textCollapse)) {
            textCollapse = getContext().getString(R.string.shink);
        }
        if (TextUtils.isEmpty(textExpand)) {
            textExpand = getContext().getString(R.string.expand);
        }
        contentTextColor = typedArray.getColor(R.styleable.ExpandableTextView_contentTextColor,
                ContextCompat.getColor(getContext(), R.color.light_gray));
        contentTextSize = typedArray.getDimension(R.styleable.ExpandableTextView_contentTextSize,
                DisplayUtil.sp2px(14));

        collapseExpandTextColor = typedArray.getColor(R.styleable.ExpandableTextView_collapseExpandTextColor,
                ContextCompat.getColor(getContext(), R.color.main_color));
        collapseExpandTextSize = typedArray.getDimension(R.styleable.ExpandableTextView_collapseExpandTextSize,
                DisplayUtil.sp2px(14));

        grarity = typedArray.getInt(R.styleable.ExpandableTextView_collapseExpandGrarity, Gravity.LEFT);

        typedArray.recycle();
        // enforces vertical orientation
        setOrientation(LinearLayout.VERTICAL);
        // default visibility is gone
        setVisibility(GONE);
    }

    /**
     * ??view
     */
    @Override
    protected void onFinishInflate() {
        findViews();
    }

    /**
     * ?viwe
     */
    private void findViews() {
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.item_expand_shink, this);
        mTvContent = (TextView) findViewById(R.id.expandable_text);
        mTvContent.setOnClickListener(this);
        mTvExpandCollapse = (TextView) findViewById(R.id.expand_collapse);
        if (showExpandCollapseDrawable) {
            mTvExpandCollapse.setCompoundDrawablesWithIntrinsicBounds(null, null,
                    mCollapsed ? mExpandDrawable : mCollapseDrawable, null);
        }
        mTvExpandCollapse.setText(
                mCollapsed ? getResources().getString(R.string.expand) : getResources().getString(R.string.shink));
        mTvExpandCollapse.setOnClickListener(this);

        mTvContent.setTextColor(contentTextColor);
        mTvContent.getPaint().setTextSize(contentTextSize);

        mTvExpandCollapse.setTextColor(collapseExpandTextColor);
        mTvExpandCollapse.getPaint().setTextSize(collapseExpandTextSize);

        //??
        LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        lp.gravity = grarity;
        mTvExpandCollapse.setLayoutParams(lp);
    }

    /**
     * 
     * @param view
     */
    @Override
    public void onClick(View view) {
        if (mTvExpandCollapse.getVisibility() != View.VISIBLE) {
            return;
        }
        mCollapsed = !mCollapsed;
        ///?
        if (showExpandCollapseDrawable) {
            mTvExpandCollapse.setCompoundDrawablesWithIntrinsicBounds(null, null,
                    mCollapsed ? mExpandDrawable : mCollapseDrawable, null);
        }
        mTvExpandCollapse.setText(
                mCollapsed ? getResources().getString(R.string.expand) : getResources().getString(R.string.shink));
        //???
        if (mCollapsedStatus != null) {
            mCollapsedStatus.put(mPosition, mCollapsed);
        }
        // /
        mAnimating = true;
        ValueAnimator valueAnimator;
        if (mCollapsed) {
            //            mTvContent.setMaxLines(mMaxCollapsedLines);
            valueAnimator = new ValueAnimator().ofInt(getHeight(), mCollapsedHeight);
        } else {
            valueAnimator = new ValueAnimator().ofInt(getHeight(),
                    getHeight() + mTextHeightWithMaxLines - mTvContent.getHeight());
        }
        valueAnimator.addUpdateListener(valueAnimator1 -> {
            int animatedValue = (int) valueAnimator1.getAnimatedValue();
            mTvContent.setMaxHeight(animatedValue - mMarginBetweenTxtAndBottom);
            getLayoutParams().height = animatedValue;
            requestLayout();
        });
        valueAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                // ??????
                /// clear the animation flag
                mAnimating = false;
                // notify the listener
                if (mListener != null) {
                    mListener.onExpandStateChanged(mTvContent, !mCollapsed);
                }
            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        valueAnimator.setDuration(mAnimationDuration);
        valueAnimator.start();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // ??child?
        return mAnimating;
    }

    /**
     * ??
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // If no change, measure and return
        if (!mRelayout || getVisibility() == View.GONE) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }
        mRelayout = false;

        // Setup with optimistic case
        // i.e. Everything fits. No button needed
        mTvExpandCollapse.setVisibility(View.GONE);
        mTvContent.setMaxLines(Integer.MAX_VALUE);

        // Measure
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //???
        if (mTvContent.getLineCount() <= mMaxCollapsedLines) {
            return;
        }
        // ?tv?padding
        mTextHeightWithMaxLines = getRealTextViewHeight(mTvContent);

        // ??
        if (mCollapsed) {
            mTvContent.setMaxLines(mMaxCollapsedLines);
        }
        mTvExpandCollapse.setVisibility(View.VISIBLE);

        // Re-measure with new setup
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (mCollapsed) {
            // Gets the margin between the TextView's bottom and the ViewGroup's bottom
            mTvContent.post(() -> mMarginBetweenTxtAndBottom = getHeight() - mTvContent.getHeight());
            // ??
            mCollapsedHeight = getMeasuredHeight();
        }
    }

    /**
     * ?tv?padding
     * @param textView
     * @return
     */
    private static int getRealTextViewHeight(TextView textView) {
        int textHeight = textView.getLayout().getLineTop(textView.getLineCount());
        int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();
        return textHeight + padding;
    }

    /********************/

    /**
     * /?
     * @param listener
     */
    public void setOnExpandStateChangeListener(OnExpandStateChangeListener listener) {
        mListener = listener;
    }

    /**
     * 
     * @param text
     */
    public void setText(CharSequence text) {
        mRelayout = true;
        mTvContent.setText(text);
        setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
    }

    /**
     * ??/?
     * @param text
     * @param position
     */
    public void setText(CharSequence text, int position) {
        mPosition = position;
        //??true:
        mCollapsed = mCollapsedStatus.get(position, true);
        clearAnimation();
        ///
        if (showExpandCollapseDrawable) {
            mTvExpandCollapse.setCompoundDrawablesWithIntrinsicBounds(null, null,
                    mCollapsed ? mExpandDrawable : mCollapseDrawable, null);
        }
        mTvExpandCollapse.setText(
                mCollapsed ? getResources().getString(R.string.expand) : getResources().getString(R.string.shink));

        setText(text);
        getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
        requestLayout();
    }

    /**
     * ?
     * @return
     */
    public CharSequence getText() {
        if (mTvContent == null) {
            return "";
        }
        return mTvContent.getText();
    }

    /**
     * ???
     */
    public interface OnExpandStateChangeListener {
        /**
         * @param textView   - TextView being expanded/collapsed
         * @param isExpanded - true if the TextView has been expanded
         */
        void onExpandStateChanged(TextView textView, boolean isExpanded);
    }
}