From ca9876b1a9c95e842aa9ede272ef375c34e3f802 Mon Sep 17 00:00:00 2001 From: gxl Date: Fri, 24 Aug 2018 14:22:50 +0800 Subject: [PATCH] add ExpandableTextView --- .../java/com/yesway/android/MainActivity.java | 12 +- .../android/view/ExpandableTextView.java | 510 ++++++++++++++++++ .../android/view/TouchyRecyclerView.java | 41 ++ app/src/main/res/layout/activity_main.xml | 58 +- app/src/main/res/mipmap-xhdpi/expend_btn.png | Bin 0 -> 1732 bytes app/src/main/res/mipmap-xhdpi/reduce_btn.png | Bin 0 -> 610 bytes app/src/main/res/values/attrs.xml | 14 + app/src/main/res/values/ids.xml | 6 + 8 files changed, 609 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/yesway/android/view/ExpandableTextView.java create mode 100644 app/src/main/java/com/yesway/android/view/TouchyRecyclerView.java create mode 100644 app/src/main/res/mipmap-xhdpi/expend_btn.png create mode 100644 app/src/main/res/mipmap-xhdpi/reduce_btn.png create mode 100644 app/src/main/res/values/ids.xml diff --git a/app/src/main/java/com/yesway/android/MainActivity.java b/app/src/main/java/com/yesway/android/MainActivity.java index c6dd225..c25d67f 100644 --- a/app/src/main/java/com/yesway/android/MainActivity.java +++ b/app/src/main/java/com/yesway/android/MainActivity.java @@ -6,6 +6,7 @@ import com.yesway.android.net.ApiManager; import com.yesway.android.net.dto.response.LoginResponse; import com.yesway.android.net.http.Response; import com.yesway.android.net.model.IUserModel; +import com.yesway.android.view.ExpandableTextView; import android.content.Intent; import android.os.Bundle; @@ -20,15 +21,8 @@ public class MainActivity extends BaseActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - findViewById(R.id.btn).setOnClickListener(new View.OnClickListener(){ - @Override - public void onClick(View v) { - Intent intent = new Intent("android.intent.action.second_activity"); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - }); - + ExpandableTextView expandableTextView = findViewById(R.id.expand_text_view); + expandableTextView.setText("我们为全球首个人工智能区块链数字资产交易平台(全球前十)提供全面技术支持服务\n" + "公司以区块链技术开发应用为核心业务方向,获得一线资本千万美元的投资,全力打造全球区块链行业中,安全、透明的区块链资产技术服务平台是我们的企业使命"); } @Override diff --git a/app/src/main/java/com/yesway/android/view/ExpandableTextView.java b/app/src/main/java/com/yesway/android/view/ExpandableTextView.java new file mode 100644 index 0000000..efb84f6 --- /dev/null +++ b/app/src/main/java/com/yesway/android/view/ExpandableTextView.java @@ -0,0 +1,510 @@ +/* + * 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.yesway.android.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Handler; +import android.support.annotation.DrawableRes; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.SparseBooleanArray; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.Transformation; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.yesway.android.R; + +/** + * 默认展开状态: ExpandableTextView.expandView(); ExpandableTextView.showToogleView(false); + */ +public class ExpandableTextView extends LinearLayout implements View.OnClickListener { + + private static final String TAG = ExpandableTextView.class.getSimpleName(); + + private static final int EXPAND_INDICATOR_IMAGE_BUTTON = 0; + + private static final int EXPAND_INDICATOR_TEXT_VIEW = 1; + + private static final int DEFAULT_TOGGLE_TYPE = EXPAND_INDICATOR_IMAGE_BUTTON; + + /* 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; + + /* The default alpha value when the animation starts */ + private static final float DEFAULT_ANIM_ALPHA_START = 0.7f; + + protected TextView mTv; + + protected View mToggleView; // View 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 ExpandIndicatorController mExpandIndicatorController; + + private int mAnimationDuration; + + private float mAnimAlphaStart; + + private boolean mAnimating; + + @IdRes + private int mExpandableTextId = R.id.expandable_text; + + @IdRes + private int mExpandCollapseToggleId = R.id.expand_collapse; + + private boolean mExpandToggleOnTextClick; + + /* Listener for callback */ + private OnExpandStateChangeListener mListener; + + /* For saving collapsed status when used in ListView */ + private SparseBooleanArray mCollapsedStatus; + private int mPosition; + + 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 (mToggleView.getVisibility() != View.VISIBLE) { + return; + } + + mCollapsed = !mCollapsed; + mExpandIndicatorController.changeState(mCollapsed); + + if (mCollapsedStatus != null) { + mCollapsedStatus.put(mPosition, mCollapsed); + } + + // 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) { + applyAlphaAnimation(mTv, mAnimAlphaStart); + } + + @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; + + // Setup with optimistic case + // i.e. Everything fits. No button needed + mToggleView.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); + + // Doesn't fit in collapsed mode. Collapse text view as needed. Show + // button. + if (mCollapsed) { + mTv.setMaxLines(mMaxCollapsedLines); + } + mToggleView.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(new Runnable() { + @Override + public void run() { + 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); + clearAnimation(); + getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; + requestLayout(); + } + + public void setText(@Nullable CharSequence text, @NonNull SparseBooleanArray collapsedStatus, + int position) { + mCollapsedStatus = collapsedStatus; + mPosition = position; + boolean isCollapsed = collapsedStatus.get(position, true); + clearAnimation(); + mCollapsed = isCollapsed; + mExpandIndicatorController.changeState(mCollapsed); + setText(text); + } + + @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); + mAnimAlphaStart = typedArray.getFloat(R.styleable.ExpandableTextView_animAlphaStart, + DEFAULT_ANIM_ALPHA_START); + mExpandableTextId = typedArray.getResourceId(R.styleable.ExpandableTextView_expandableTextId, + R.id.expandable_text); + mExpandCollapseToggleId = typedArray + .getResourceId(R.styleable.ExpandableTextView_expandCollapseToggleId, R.id.expand_collapse); + mExpandToggleOnTextClick = + typedArray.getBoolean(R.styleable.ExpandableTextView_expandToggleOnTextClick, true); + + mExpandIndicatorController = setupExpandToggleController(getContext(), typedArray); + + typedArray.recycle(); + + // enforces vertical orientation + setOrientation(LinearLayout.VERTICAL); + + // default visibility is gone + setVisibility(GONE); + } + + private void findViews() { + mTv = (TextView) findViewById(mExpandableTextId); + if (mExpandToggleOnTextClick) { + mTv.setOnClickListener(this); + } else { + mTv.setOnClickListener(null); + } + mToggleView = findViewById(mExpandCollapseToggleId); + mExpandIndicatorController.setView(mToggleView); + mExpandIndicatorController.changeState(mCollapsed); + mToggleView.setOnClickListener(this); + } + + private static boolean isPostHoneycomb() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; + } + + private static boolean isPostLolipop() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private static void applyAlphaAnimation(View view, float alpha) { + if (isPostHoneycomb()) { + view.setAlpha(alpha); + } else { + AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha); + // make it instant + alphaAnimation.setDuration(0); + alphaAnimation.setFillAfter(true); + view.startAnimation(alphaAnimation); + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) { + Resources resources = context.getResources(); + if (isPostLolipop()) { + return resources.getDrawable(resId, context.getTheme()); + } else { + return resources.getDrawable(resId); + } + } + + private static int getRealTextViewHeight(@NonNull TextView textView) { + int textHeight = textView.getLayout().getLineTop(textView.getLineCount()); + int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom(); + return textHeight + padding; + } + + private ExpandIndicatorController setupExpandToggleController(@NonNull Context context, + TypedArray typedArray) { + final int expandToggleType = + typedArray.getInt(R.styleable.ExpandableTextView_expandToggleType, DEFAULT_TOGGLE_TYPE); + final ExpandIndicatorController expandIndicatorController; + switch (expandToggleType) { + case EXPAND_INDICATOR_IMAGE_BUTTON: + Drawable expandDrawable = + typedArray.getDrawable(R.styleable.ExpandableTextView_expandIndicator); + Drawable collapseDrawable = + typedArray.getDrawable(R.styleable.ExpandableTextView_collapseIndicator); + + if (expandDrawable == null) { + expandDrawable = getDrawable(context, R.mipmap.expend_btn); + } + if (collapseDrawable == null) { + collapseDrawable = getDrawable(context, R.mipmap.reduce_btn); + } + expandIndicatorController = + new ImageButtonExpandController(expandDrawable, collapseDrawable); + break; + case EXPAND_INDICATOR_TEXT_VIEW: + String expandText = typedArray.getString(R.styleable.ExpandableTextView_expandIndicator); + String collapseText = + typedArray.getString(R.styleable.ExpandableTextView_collapseIndicator); + expandIndicatorController = new TextViewExpandController(expandText, collapseText); + break; + default: + throw new IllegalStateException( + "Must be of enum: ExpandableTextView_expandToggleType, one of EXPAND_INDICATOR_IMAGE_BUTTON or EXPAND_INDICATOR_TEXT_VIEW."); + } + + return expandIndicatorController; + } + + 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); + if (Float.compare(mAnimAlphaStart, 1.0f) != 0) { + applyAlphaAnimation(mTv, mAnimAlphaStart + interpolatedTime * (1.0f - mAnimAlphaStart)); + } + 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); + } + + interface ExpandIndicatorController { + void changeState(boolean collapsed); + + void setView(View toggleView); + + void showView(boolean isshow); + } + + class ImageButtonExpandController implements ExpandIndicatorController { + + private final Drawable mExpandDrawable; + private final Drawable mCollapseDrawable; + + private ImageButton mImageButton; + + public ImageButtonExpandController(Drawable expandDrawable, Drawable collapseDrawable) { + mExpandDrawable = expandDrawable; + mCollapseDrawable = collapseDrawable; + } + + @Override + public void changeState(boolean collapsed) { + mImageButton.setImageDrawable(collapsed ? mExpandDrawable : mCollapseDrawable); + } + + @Override + public void setView(View toggleView) { + mImageButton = (ImageButton) toggleView; + } + + @Override + public void showView(final boolean isshow) { + if (mImageButton != null) { + new Handler().post(new Runnable() { + @Override + public void run() { + mImageButton.clearAnimation(); + mImageButton.setVisibility(isshow ? View.VISIBLE : View.GONE); + } + }); + } + } + } + + static class TextViewExpandController implements ExpandIndicatorController { + + private final String mExpandText; + private final String mCollapseText; + + private TextView mTextView; + + public TextViewExpandController(String expandText, String collapseText) { + mExpandText = expandText; + mCollapseText = collapseText; + } + + @Override + public void changeState(boolean collapsed) { + mTextView.setText(collapsed ? mExpandText : mCollapseText); + } + + @Override + public void setView(View toggleView) { + mTextView = (TextView) toggleView; + } + + @Override + public void showView(boolean isshow) { + + } + } + + public void showToogleView(boolean isshow) { + if (mExpandIndicatorController != null) { + mExpandIndicatorController.showView(isshow); + } + } + + public void expandView() { + mCollapsed = false; + requestLayout(); + } +} diff --git a/app/src/main/java/com/yesway/android/view/TouchyRecyclerView.java b/app/src/main/java/com/yesway/android/view/TouchyRecyclerView.java new file mode 100644 index 0000000..943db65 --- /dev/null +++ b/app/src/main/java/com/yesway/android/view/TouchyRecyclerView.java @@ -0,0 +1,41 @@ +package com.yesway.android.view; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.MotionEvent; + +/** + * Description:
+ * Author: gxl
+ * Date: 2018/8/24
+ * Version: V1.0.0
+ * Update:
+ */ +public class TouchyRecyclerView extends RecyclerView { + private OnNoChildClickListener listener; + + public TouchyRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public interface OnNoChildClickListener { + public void onNoChildClick(); + } + + public void setOnNoChildClickListener(OnNoChildClickListener listener) { + this.listener = listener; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + + if (event.getAction() == MotionEvent.ACTION_DOWN + && findChildViewUnder(event.getX(), event.getY()) == null) { + if (listener != null) { + listener.onNoChildClick(); + } + } + return super.dispatchTouchEvent(event); + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2e01240..d1949d2 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,40 +1,52 @@ - + android:orientation="vertical"> -