Java tutorial
package com.rashwan.reactive_popular_movies.common.utilities; /* * 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. */ 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.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.Transformation; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import com.rashwan.reactive_popular_movies.R; public class ExpandableTextView extends LinearLayout implements View.OnClickListener { private static final String TAG = ExpandableTextView.class.getSimpleName(); /* The default number of lines */ private static final int MAX_COLLAPSED_LINES = 8; /* The default animation duration */ private static final int DEFAULT_ANIM_DURATION = 300; protected TextView mTv; protected ImageButton mButton; // Button to expand/collapse private boolean mRelayout; private boolean mCollapsed = true; // Show short version as default. private int mCollapsedHeight; private int mTextHeightWithMaxLines; private int mMaxCollapsedLines; private int mMarginBetweenTxtAndBottom; private Drawable mExpandDrawable; private Drawable mCollapseDrawable; private int mAnimationDuration; private boolean mAnimating; /* Listener for callback */ private OnExpandStateChangeListener mListener; 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); } @Override public void onClick(View view) { if (mButton.getVisibility() != View.VISIBLE) { return; } mCollapsed = !mCollapsed; mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable); // mark that the animation is in progress mAnimating = true; Animation animation; if (mCollapsed) { animation = new ExpandCollapseAnimation(this, getHeight(), mCollapsedHeight); } else { animation = new ExpandCollapseAnimation(this, getHeight(), getHeight() + mTextHeightWithMaxLines - mTv.getHeight()); } animation.setFillAfter(true); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { // clear animation here to avoid repeated applyTransformation() calls clearAnimation(); // clear the animation flag mAnimating = false; // notify the listener if (mListener != null) { mListener.onExpandStateChanged(mTv, !mCollapsed); } } @Override public void onAnimationRepeat(Animation animation) { } }); clearAnimation(); startAnimation(animation); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // while an animation is in progress, intercept all the touch events to children to // prevent extra clicks during the animation return mAnimating; } @Override protected void onFinishInflate() { super.onFinishInflate(); findViews(); } @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; int parentPadding = getPaddingTop() + getPaddingBottom(); // Setup with optimistic case // i.e. Everything fits. No button needed mButton.setVisibility(View.GONE); mTv.setMaxLines(Integer.MAX_VALUE); // Measure super.onMeasure(widthMeasureSpec, heightMeasureSpec); // If the text fits in collapsed mode, we are done. if (mTv.getLineCount() <= mMaxCollapsedLines) { return; } // Saves the text height w/ max lines mTextHeightWithMaxLines = getRealTextViewHeight(mTv, parentPadding); // Doesn't fit in collapsed mode. Collapse text view as needed. Show // button. if (mCollapsed) { mTv.setMaxLines(mMaxCollapsedLines); } mButton.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 mTv.post(() -> mMarginBetweenTxtAndBottom = getHeight() - mTv.getHeight()); // Saves the collapsed height of this ViewGroup mCollapsedHeight = getMeasuredHeight(); } } public void setOnExpandStateChangeListener(@Nullable OnExpandStateChangeListener listener) { mListener = listener; } public void setText(@Nullable CharSequence text) { mRelayout = true; mTv.setText(text); setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE); } @Nullable public CharSequence getText() { if (mTv == null) { return ""; } return mTv.getText(); } private void init(AttributeSet attrs) { 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); if (mExpandDrawable == null) { mExpandDrawable = getDrawable(getContext(), R.drawable.ic_expand_more_white_12dp); } if (mCollapseDrawable == null) { mCollapseDrawable = getDrawable(getContext(), R.drawable.ic_expand_less_white_12dp); } typedArray.recycle(); // enforces vertical orientation setOrientation(LinearLayout.VERTICAL); // default visibility is gone setVisibility(GONE); } private void findViews() { mTv = (TextView) findViewById(R.id.expandable_text); mTv.setOnClickListener(this); mButton = (ImageButton) findViewById(R.id.expand_collapse); mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable); mButton.setOnClickListener(this); } private static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) { return ContextCompat.getDrawable(context, resId); } private static int getRealTextViewHeight(@NonNull TextView textView, int parentPadding) { int textHeight = textView.getLayout().getLineTop(textView.getLineCount()); int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom(); return textHeight + padding + parentPadding; } private class ExpandCollapseAnimation extends Animation { private final View mTargetView; private final int mStartHeight; private final int mEndHeight; public ExpandCollapseAnimation(View view, int startHeight, int endHeight) { mTargetView = view; mStartHeight = startHeight; mEndHeight = endHeight; setDuration(mAnimationDuration); } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { final int newHeight = (int) ((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight); mTv.setMaxHeight(newHeight - mMarginBetweenTxtAndBottom); mTargetView.getLayoutParams().height = newHeight; mTargetView.requestLayout(); } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); } @Override public boolean willChangeBounds() { return true; } } public interface OnExpandStateChangeListener { /** * Called when the expand/collapse animation has been finished * * @param textView - TextView being expanded/collapsed * @param isExpanded - true if the TextView has been expanded */ void onExpandStateChanged(TextView textView, boolean isExpanded); } }