├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── ExpandableTextView ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── jaydenxiao │ │ └── com │ │ └── expandabletextview │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── jaydenxiao │ │ │ └── com │ │ │ └── expandabletextview │ │ │ ├── ExpandableTextView.java │ │ │ └── UIUtil.java │ └── res │ │ ├── drawable-xxhdpi │ │ ├── icon_green_arrow_down.png │ │ └── icon_green_arrow_up.png │ │ ├── layout │ │ └── item_expand_collapse.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ └── strings.xml │ └── test │ └── java │ └── jaydenxiao │ └── com │ └── expandabletextview │ └── ExampleUnitTest.java ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jaydenxiao │ │ └── expandabletextview │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jaydenxiao │ │ │ └── expandabletextviewexample │ │ │ ├── ListAdapter.java │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fra_listview.xml │ │ ├── fra_scrollview.xml │ │ └── item_list.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── jaydenxiao │ └── expandabletextview │ └── ExampleUnitTest.java ├── arts ├── expandabletextview.gif └── qrcode_for_gh_e90bfe968c01_430.jpg ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ExpandableTextViewExample -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ExpandableTextView/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ExpandableTextView/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | ext{ 3 | PUBLISH_GROUP_ID = 'com.jaydenxiao' 4 | PUBLISH_ARTIFACT_ID = 'ExpandableTextView' 5 | PUBLISH_VERSION = '1.0.0' 6 | } 7 | android { 8 | compileSdkVersion 23 9 | buildToolsVersion "24.0.0 rc4" 10 | 11 | defaultConfig { 12 | minSdkVersion 15 13 | targetSdkVersion 23 14 | versionCode 1 15 | versionName "1.0.0" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | //打包不检查 24 | lintOptions { 25 | checkReleaseBuilds false 26 | abortOnError false 27 | } 28 | } 29 | 30 | dependencies { 31 | compile fileTree(dir: 'libs', include: ['*.jar']) 32 | testCompile 'junit:junit:4.12' 33 | compile 'com.android.support:appcompat-v7:23.4.0' 34 | } 35 | apply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle' 36 | -------------------------------------------------------------------------------- /ExpandableTextView/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in H:\android-22\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /ExpandableTextView/src/androidTest/java/jaydenxiao/com/expandabletextview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package jaydenxiao.com.expandabletextview; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /ExpandableTextView/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ExpandableTextView/src/main/java/jaydenxiao/com/expandabletextview/ExpandableTextView.java: -------------------------------------------------------------------------------- 1 | 2 | package jaydenxiao.com.expandabletextview; 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.annotation.TargetApi; 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.graphics.drawable.Drawable; 9 | import android.os.Build; 10 | import android.support.v4.content.ContextCompat; 11 | import android.text.TextUtils; 12 | import android.util.AttributeSet; 13 | import android.util.SparseBooleanArray; 14 | import android.view.Gravity; 15 | import android.view.LayoutInflater; 16 | import android.view.MotionEvent; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.widget.LinearLayout; 20 | import android.widget.TextView; 21 | 22 | /** 23 | * des:可伸展textview 24 | * Created by xsf 25 | * on 2016.08.24 26 | */ 27 | public class ExpandableTextView extends LinearLayout implements View.OnClickListener { 28 | 29 | /* 默认最高行数 */ 30 | private static final int MAX_COLLAPSED_LINES = 5; 31 | 32 | /* 默认动画执行时间 */ 33 | private static final int DEFAULT_ANIM_DURATION = 200; 34 | 35 | /*内容textview*/ 36 | protected TextView mTvContent; 37 | 38 | /*展开收起textview*/ 39 | protected TextView mTvExpandCollapse; 40 | 41 | /*是否有重新绘制*/ 42 | private boolean mRelayout; 43 | 44 | /*默认收起*/ 45 | private boolean mCollapsed = true; 46 | 47 | /*展开图片*/ 48 | private Drawable mExpandDrawable; 49 | /*收起图片*/ 50 | private Drawable mCollapseDrawable; 51 | /*动画执行时间*/ 52 | private int mAnimationDuration; 53 | /*是否正在执行动画*/ 54 | private boolean mAnimating; 55 | /* 展开收起状态回调 */ 56 | private OnExpandStateChangeListener mListener; 57 | /* listview等列表情况下保存每个item的收起/展开状态 */ 58 | private SparseBooleanArray mCollapsedStatus; 59 | /* 列表位置 */ 60 | private int mPosition; 61 | 62 | /*设置内容最大行数,超过隐藏*/ 63 | private int mMaxCollapsedLines; 64 | 65 | /*这个linerlayout容器的高度*/ 66 | private int mCollapsedHeight; 67 | 68 | /*内容tv真实高度(含padding)*/ 69 | private int mTextHeightWithMaxLines; 70 | 71 | /*内容tvMarginTopAmndBottom高度*/ 72 | private int mMarginBetweenTxtAndBottom; 73 | 74 | /*内容颜色*/ 75 | private int contentTextColor; 76 | /*收起展开颜色*/ 77 | private int collapseExpandTextColor; 78 | /*内容字体大小*/ 79 | private float contentTextSize; 80 | /*收起展字体大小*/ 81 | private float collapseExpandTextSize; 82 | /*收起文字*/ 83 | private String textCollapse; 84 | /*展开文字*/ 85 | private String textExpand; 86 | 87 | /*收起展开位置,默认左边*/ 88 | private int grarity; 89 | 90 | /*收起展开图标位置,默认在右边*/ 91 | private int drawableGrarity; 92 | 93 | public ExpandableTextView(Context context) { 94 | this(context, null); 95 | } 96 | 97 | public ExpandableTextView(Context context, AttributeSet attrs) { 98 | super(context, attrs); 99 | init(attrs); 100 | } 101 | 102 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 103 | public ExpandableTextView(Context context, AttributeSet attrs, int defStyle) { 104 | super(context, attrs, defStyle); 105 | init(attrs); 106 | } 107 | 108 | @Override 109 | public void setOrientation(int orientation) { 110 | if (LinearLayout.HORIZONTAL == orientation) { 111 | throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation."); 112 | } 113 | super.setOrientation(orientation); 114 | } 115 | /** 116 | * 初始化属性 117 | * @param attrs 118 | */ 119 | private void init(AttributeSet attrs) { 120 | mCollapsedStatus=new SparseBooleanArray(); 121 | 122 | TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableTextView); 123 | mMaxCollapsedLines = typedArray.getInt(R.styleable.ExpandableTextView_maxCollapsedLines, MAX_COLLAPSED_LINES); 124 | mAnimationDuration = typedArray.getInt(R.styleable.ExpandableTextView_animDuration, DEFAULT_ANIM_DURATION); 125 | mExpandDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_expandDrawable); 126 | mCollapseDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_collapseDrawable); 127 | 128 | textCollapse = typedArray.getString(R.styleable.ExpandableTextView_textCollapse); 129 | textExpand = typedArray.getString(R.styleable.ExpandableTextView_textExpand); 130 | 131 | if (mExpandDrawable == null) { 132 | mExpandDrawable = ContextCompat.getDrawable(getContext(),R.drawable.icon_green_arrow_up); 133 | } 134 | if (mCollapseDrawable == null) { 135 | mCollapseDrawable = ContextCompat.getDrawable(getContext(), R.drawable.icon_green_arrow_down); 136 | } 137 | 138 | if (TextUtils.isEmpty(textCollapse)) { 139 | textCollapse = getContext().getString(R.string.collapse); 140 | } 141 | if (TextUtils.isEmpty(textExpand)) { 142 | textExpand = getContext().getString(R.string.expand); 143 | } 144 | contentTextColor = typedArray.getColor(R.styleable.ExpandableTextView_contentTextColor, ContextCompat.getColor(getContext(), R.color.gray)); 145 | contentTextSize = typedArray.getDimension(R.styleable.ExpandableTextView_contentTextSize, UIUtil.sp2px(getContext(),14)); 146 | 147 | collapseExpandTextColor = typedArray.getColor(R.styleable.ExpandableTextView_collapseExpandTextColor, ContextCompat.getColor(getContext(), R.color.main_color)); 148 | collapseExpandTextSize = typedArray.getDimension(R.styleable.ExpandableTextView_collapseExpandTextSize, UIUtil.sp2px(getContext(),14)); 149 | 150 | grarity = typedArray.getInt(R.styleable.ExpandableTextView_collapseExpandGrarity, Gravity.LEFT); 151 | drawableGrarity=typedArray.getInt(R.styleable.ExpandableTextView_drawableGrarity, Gravity.RIGHT); 152 | 153 | typedArray.recycle(); 154 | // enforces vertical orientation 155 | setOrientation(LinearLayout.VERTICAL); 156 | // default visibility is gone 157 | setVisibility(GONE); 158 | } 159 | 160 | /** 161 | * 渲染完成时初始化view 162 | */ 163 | @Override 164 | protected void onFinishInflate() { 165 | findViews(); 166 | } 167 | 168 | /** 169 | * 初始化viwe 170 | */ 171 | private void findViews() { 172 | LayoutInflater inflater = (LayoutInflater) getContext() 173 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 174 | inflater.inflate(R.layout.item_expand_collapse, this); 175 | mTvContent = (TextView) findViewById(R.id.expandable_text); 176 | mTvContent.setOnClickListener(this); 177 | mTvExpandCollapse = (TextView) findViewById(R.id.expand_collapse); 178 | setDrawbleAndText(); 179 | mTvExpandCollapse.setOnClickListener(this); 180 | 181 | mTvContent.setTextColor(contentTextColor); 182 | mTvContent.getPaint().setTextSize(contentTextSize); 183 | 184 | mTvExpandCollapse.setTextColor(collapseExpandTextColor); 185 | mTvExpandCollapse.getPaint().setTextSize(collapseExpandTextSize); 186 | 187 | //设置收起展开位置:左或者右 188 | LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); 189 | lp.gravity = grarity; 190 | mTvExpandCollapse.setLayoutParams(lp); 191 | } 192 | /** 193 | * 点击事件 194 | * @param view 195 | */ 196 | @Override 197 | public void onClick(View view) { 198 | if (mTvExpandCollapse.getVisibility() != View.VISIBLE) { 199 | return; 200 | } 201 | mCollapsed = !mCollapsed; 202 | //修改收起/展开图标、文字 203 | setDrawbleAndText(); 204 | //保存位置状态 205 | if (mCollapsedStatus != null) { 206 | mCollapsedStatus.put(mPosition, mCollapsed); 207 | } 208 | // 执行展开/收起动画 209 | mAnimating = true; 210 | ValueAnimator valueAnimator; 211 | if (mCollapsed) { 212 | // mTvContent.setMaxLines(mMaxCollapsedLines); 213 | valueAnimator = new ValueAnimator().ofInt(getHeight(), mCollapsedHeight); 214 | } else { 215 | valueAnimator = new ValueAnimator().ofInt(getHeight(), getHeight() + 216 | mTextHeightWithMaxLines - mTvContent.getHeight()); 217 | } 218 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){ 219 | @Override 220 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 221 | int animatedValue = (int) valueAnimator.getAnimatedValue(); 222 | mTvContent.setMaxHeight(animatedValue - mMarginBetweenTxtAndBottom); 223 | getLayoutParams().height = animatedValue; 224 | requestLayout(); 225 | } 226 | }); 227 | valueAnimator.addListener(new Animator.AnimatorListener() { 228 | @Override 229 | public void onAnimationStart(Animator animator) { 230 | 231 | } 232 | @Override 233 | public void onAnimationEnd(Animator animator) { 234 | // 动画结束后发送结束的信号 235 | /// clear the animation flag 236 | mAnimating = false; 237 | // notify the listener 238 | if (mListener != null) { 239 | mListener.onExpandStateChanged(mTvContent, !mCollapsed); 240 | } 241 | } 242 | @Override 243 | public void onAnimationCancel(Animator animator) { 244 | 245 | } 246 | @Override 247 | public void onAnimationRepeat(Animator animator) { 248 | 249 | } 250 | }); 251 | valueAnimator.setDuration(mAnimationDuration); 252 | valueAnimator.start(); 253 | } 254 | 255 | @Override 256 | public boolean onInterceptTouchEvent(MotionEvent ev) { 257 | // 当动画还在执行状态时,拦截事件,不让child处理 258 | return mAnimating; 259 | } 260 | /** 261 | * 重新测量 262 | * @param widthMeasureSpec 263 | * @param heightMeasureSpec 264 | */ 265 | @Override 266 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 267 | // If no change, measure and return 268 | if (!mRelayout || getVisibility() == View.GONE) { 269 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 270 | return; 271 | } 272 | mRelayout = false; 273 | 274 | // Setup with optimistic case 275 | // i.e. Everything fits. No button needed 276 | mTvExpandCollapse.setVisibility(View.GONE); 277 | mTvContent.setMaxLines(Integer.MAX_VALUE); 278 | 279 | // Measure 280 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 281 | 282 | //如果内容真实行数小于等于最大行数,不处理 283 | if (mTvContent.getLineCount() <= mMaxCollapsedLines) { 284 | return; 285 | } 286 | // 获取内容tv真实高度(含padding) 287 | mTextHeightWithMaxLines = getRealTextViewHeight(mTvContent); 288 | 289 | // 如果是收起状态,重新设置最大行数 290 | if (mCollapsed) { 291 | mTvContent.setMaxLines(mMaxCollapsedLines); 292 | } 293 | mTvExpandCollapse.setVisibility(View.VISIBLE); 294 | 295 | // Re-measure with new setup 296 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 297 | 298 | if (mCollapsed) { 299 | // Gets the margin between the TextView's bottom and the ViewGroup's bottom 300 | mTvContent.post(new Runnable() { 301 | @Override 302 | public void run() { 303 | mMarginBetweenTxtAndBottom = getHeight() - mTvContent.getHeight(); 304 | } 305 | }); 306 | // 保存这个容器的测量高度 307 | mCollapsedHeight = getMeasuredHeight(); 308 | } 309 | } 310 | /** 311 | * 获取内容tv真实高度(含padding) 312 | * @param textView 313 | * @return 314 | */ 315 | private static int getRealTextViewHeight( TextView textView) { 316 | int textHeight = textView.getLayout().getLineTop(textView.getLineCount()); 317 | int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom(); 318 | return textHeight + padding; 319 | } 320 | 321 | /** 322 | * 设置收起展开图标位置和文字 323 | */ 324 | private void setDrawbleAndText(){ 325 | if(Gravity.LEFT==drawableGrarity){ 326 | mTvExpandCollapse.setCompoundDrawablesWithIntrinsicBounds(mCollapsed ? mCollapseDrawable : mExpandDrawable,null,null,null); 327 | }else{ 328 | mTvExpandCollapse.setCompoundDrawablesWithIntrinsicBounds(null,null,mCollapsed ? mCollapseDrawable : mExpandDrawable,null); 329 | } 330 | mTvExpandCollapse.setText(mCollapsed ? getResources().getString(R.string.expand) : getResources().getString(R.string.collapse)); 331 | } 332 | 333 | 334 | /*********暴露给外部调用方法***********/ 335 | 336 | /** 337 | * 设置收起/展开监听 338 | * @param listener 339 | */ 340 | public void setOnExpandStateChangeListener( OnExpandStateChangeListener listener) { 341 | mListener = listener; 342 | } 343 | 344 | /** 345 | * 设置内容 346 | * @param text 347 | */ 348 | public void setText( CharSequence text) { 349 | mRelayout = true; 350 | mTvContent.setText(text); 351 | setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE); 352 | } 353 | 354 | /** 355 | * 设置内容,列表情况下,带有保存位置收起/展开状态 356 | * @param text 357 | * @param position 358 | */ 359 | public void setText( CharSequence text,int position) { 360 | mPosition = position; 361 | //获取状态,如无,默认是true:收起 362 | mCollapsed = mCollapsedStatus.get(position, true); 363 | clearAnimation(); 364 | //设置收起/展开图标和文字 365 | setDrawbleAndText(); 366 | mTvExpandCollapse.setText(mCollapsed ? getResources().getString(R.string.expand) : getResources().getString(R.string.collapse)); 367 | 368 | setText(text); 369 | getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; 370 | requestLayout(); 371 | } 372 | 373 | /** 374 | * 获取内容 375 | * @return 376 | */ 377 | public CharSequence getText() { 378 | if (mTvContent == null) { 379 | return ""; 380 | } 381 | return mTvContent.getText(); 382 | } 383 | 384 | /** 385 | * 定义状态改变接口 386 | */ 387 | public interface OnExpandStateChangeListener { 388 | /** 389 | * @param textView - TextView being expanded/collapsed 390 | * @param isExpanded - true if the TextView has been expanded 391 | */ 392 | void onExpandStateChanged(TextView textView, boolean isExpanded); 393 | } 394 | } -------------------------------------------------------------------------------- /ExpandableTextView/src/main/java/jaydenxiao/com/expandabletextview/UIUtil.java: -------------------------------------------------------------------------------- 1 | package jaydenxiao.com.expandabletextview; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.view.View; 6 | import android.view.ViewTreeObserver; 7 | import android.view.ViewTreeObserver.OnGlobalLayoutListener; 8 | import android.view.WindowManager; 9 | import android.widget.LinearLayout; 10 | 11 | /** 12 | * des:屏幕相关辅助类 13 | * Created by xsf 14 | * on 2016.08.24 15 | */ 16 | public class UIUtil { 17 | private UIUtil() { 18 | /* cannot be instantiated */ 19 | throw new UnsupportedOperationException("cannot be instantiated"); 20 | } 21 | 22 | /** 23 | * 将px值转换为dip或dp值,保证尺寸大小不变 24 | * 25 | * @param pxValue 26 | * (DisplayMetrics类中属性density) 27 | * @return 28 | */ 29 | public static int px2dip(Context context,float pxValue) { 30 | final float scale = context.getResources().getDisplayMetrics().density; 31 | return (int) (pxValue / scale + 0.5f); 32 | } 33 | 34 | /** 35 | * 将dip或dp值转换为px值,保证尺寸大小不变 36 | * 37 | * @param dipValue 38 | * @return 39 | */ 40 | public static int dip2px( Context context ,float dipValue) { 41 | final float scale = context.getResources().getDisplayMetrics().density; 42 | return (int) (dipValue * scale + 0.5f); 43 | } 44 | 45 | /** 46 | * 将px值转换为sp值,保证文字大小不变 47 | * 48 | * @param pxValue 49 | * @return 50 | */ 51 | public static int px2sp(Context context,float pxValue) { 52 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 53 | return (int) (pxValue / fontScale + 0.5f); 54 | } 55 | 56 | /** 57 | * 将sp值转换为px值,保证文字大小不变 58 | * 59 | * @param spValue 60 | * @return 61 | */ 62 | public static int sp2px(Context context,float spValue) { 63 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 64 | return (int) (spValue * fontScale + 0.5f); 65 | } 66 | 67 | /** 68 | * 直接获取控件的宽、高 69 | * @param view 70 | * @return int[] 71 | */ 72 | public static int[] getWidgetWH(final View view){ 73 | ViewTreeObserver vto2 = view.getViewTreeObserver(); 74 | vto2.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 75 | @Override 76 | public void onGlobalLayout() { 77 | view.getViewTreeObserver().removeGlobalOnLayoutListener(this); 78 | } 79 | }); 80 | return new int[]{view.getWidth(),view.getHeight()}; 81 | } 82 | 83 | /** 84 | * 直接获取控件的宽、高 85 | * @param view 86 | * @return int[] 87 | */ 88 | public static int getViewHeight(final View view){ 89 | ViewTreeObserver vto2 = view.getViewTreeObserver(); 90 | vto2.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 91 | @Override 92 | public void onGlobalLayout() { 93 | view.getViewTreeObserver().removeGlobalOnLayoutListener(this); 94 | } 95 | }); 96 | return view.getHeight(); 97 | } 98 | 99 | /** 100 | * 直接获取控件的宽、高 101 | * @param view 102 | * @return int[] 103 | */ 104 | public static int getViewWidth(final View view){ 105 | ViewTreeObserver vto2 = view.getViewTreeObserver(); 106 | vto2.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 107 | @Override 108 | public void onGlobalLayout() { 109 | view.getViewTreeObserver().removeGlobalOnLayoutListener(this); 110 | } 111 | }); 112 | return view.getWidth(); 113 | } 114 | 115 | /** 116 | * 获得屏幕宽度 117 | * 118 | * @param context 119 | * @return 120 | */ 121 | public static int getScreenWidth(Context context) { 122 | WindowManager wm = (WindowManager) context 123 | .getSystemService(Context.WINDOW_SERVICE); 124 | DisplayMetrics outMetrics = new DisplayMetrics(); 125 | wm.getDefaultDisplay().getMetrics(outMetrics); 126 | return outMetrics.widthPixels; 127 | } 128 | 129 | /** 130 | * 获得屏幕高度 131 | * 132 | * @param context 133 | * @return 134 | */ 135 | public static int getScreenHeight(Context context) { 136 | WindowManager wm = (WindowManager) context 137 | .getSystemService(Context.WINDOW_SERVICE); 138 | DisplayMetrics outMetrics = new DisplayMetrics(); 139 | wm.getDefaultDisplay().getMetrics(outMetrics); 140 | return outMetrics.heightPixels; 141 | } 142 | 143 | /** 144 | * 获取控件的宽 145 | * @param view 146 | * @return 147 | */ 148 | public static int getWidgetWidth(View view){ 149 | int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 150 | int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 151 | view.measure(w, h);//先度量 152 | int width = view.getMeasuredWidth(); 153 | return width; 154 | } 155 | /** 156 | * 获取控件的高 157 | * @param view 158 | * @return 159 | */ 160 | public static int getWidgetHeight(View view){ 161 | int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 162 | int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 163 | view.measure(w, h);//先度量 164 | int height = view.getMeasuredHeight(); 165 | return height; 166 | } 167 | /** 168 | * 设置控件宽 169 | * @param view 170 | * @param width 171 | */ 172 | public static void setWidgetWidth(View view, int width){ 173 | LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams(); 174 | params.width = width; 175 | view.setLayoutParams(params); 176 | } 177 | /** 178 | * 设置控件高 179 | * @param view 180 | * @param height 181 | */ 182 | public static void setWidgetHeight(View view, int height){ 183 | LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams(); 184 | params.height = height; 185 | view.setLayoutParams(params); 186 | } 187 | //获得状态栏的高度 188 | public static int getStatusBarHeight(Context context) { 189 | int result = 0; 190 | int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 191 | if (resId > 0) { 192 | result = context.getResources().getDimensionPixelOffset(resId); 193 | } 194 | return result; 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /ExpandableTextView/src/main/res/drawable-xxhdpi/icon_green_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/ExpandableTextViewExample/f387755f12a2296a787754115b70d32a3bb0b832/ExpandableTextView/src/main/res/drawable-xxhdpi/icon_green_arrow_down.png -------------------------------------------------------------------------------- /ExpandableTextView/src/main/res/drawable-xxhdpi/icon_green_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/ExpandableTextViewExample/f387755f12a2296a787754115b70d32a3bb0b832/ExpandableTextView/src/main/res/drawable-xxhdpi/icon_green_arrow_up.png -------------------------------------------------------------------------------- /ExpandableTextView/src/main/res/layout/item_expand_collapse.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 16 | 30 | -------------------------------------------------------------------------------- /ExpandableTextView/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ExpandableTextView/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FA7C20 4 | #333333 5 | 6 | -------------------------------------------------------------------------------- /ExpandableTextView/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ExpandableTextView 3 | 展开 4 | 收起 5 | The story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recently married each other has gone viral, southcn.com reported. 6 | Zhou Si, the groom, and Chen Mengge, the bride, married each other on Aug 18 and will together go to Oxford University to pursue their doctoral degrees later this year. 7 | They were deskmate in middle school and then both were admitted by same high school but not in same class. 8 | 9 | -------------------------------------------------------------------------------- /ExpandableTextView/src/test/java/jaydenxiao/com/expandabletextview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package jaydenxiao.com.expandabletextview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ###非常轻量的可展开和收缩内容的TextView,可用于listview等列表或普通布局情况 2 | ###项目介绍: 3 | 一个支持可展开和收缩内容的TextView,支持在listview等列表使用不错乱,支持在普通局部使用 4 | ###国际案例:有图有真相 5 | ![效果预览图](https://github.com/jaydenxiao2016/ExpandableTextViewExample/blob/master/arts/expandabletextview.gif) 6 | ###支持配置属性: 7 | - 显示内容最大显示行数配置 8 | - 显示内容字体大小颜色配置 9 | - 展开/收起图标配置 10 | - 展开/收起位置配置 11 | - 展开/收起字体大小颜色配置 12 | - 展开/收起变化监听 13 | 14 | ###使用方法 15 | - use Gradle: 16 | 17 | ```javascript 18 | dependencies { 19 | 20 | compile 'com.jaydenxiao:ExpandableTextView:1.0.0' 21 | 22 | } 23 | ``` 24 | - Or Maven: 25 | 26 | ```javascript 27 | 28 | com.jaydenxiao 29 | ExpandableTextView 30 | 1.0.0 31 | pom 32 | 33 | ``` 34 | 35 | - Or download the libray for your module: 36 | 37 | 38 | ######布局文件 39 | ```javascript 40 | 55 | ``` 56 | 57 | ######设置text值 58 | 59 | - 普通情况 60 | expandableTextView.setText("content"); 61 | - 列表情况(把item位置传入,记录状态) 62 | expandableTextView.setText("content",position); 63 | 64 | 65 | **更多精彩文章请关注微信公众号"Android经验分享":这里将长期为您分享Android高手经验、中外开源项目、源码解析、框架设计和Android好文推荐!** 66 | 67 | 68 | ![扫一扫加我哦](https://github.com/jaydenxiao2016/ExpandableTextViewExample/blob/master/arts/qrcode_for_gh_e90bfe968c01_430.jpg) 69 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "24.0.0 rc4" 6 | 7 | defaultConfig { 8 | applicationId "com.jaydenxiao.expandabletextview" 9 | minSdkVersion 15 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.4.0' 26 | compile "com.android.support:design:23.4.0" 27 | compile project(':ExpandableTextView') 28 | } 29 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in H:\android-22\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jaydenxiao/expandabletextview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.expandabletextview; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/expandabletextviewexample/ListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.expandabletextviewexample; 2 | 3 | import android.content.Context; 4 | import android.util.SparseBooleanArray; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.BaseAdapter; 9 | 10 | import jaydenxiao.com.expandabletextview.ExpandableTextView; 11 | 12 | /** 13 | * des:适配器 14 | * Created by xsf 15 | * on 2016.08.24 16 | */ 17 | public class ListAdapter extends BaseAdapter { 18 | 19 | private final Context mContext; 20 | private final SparseBooleanArray mCollapsedStatus; 21 | private final String[] sampleStrings; 22 | 23 | public ListAdapter(Context context) { 24 | mContext = context; 25 | mCollapsedStatus = new SparseBooleanArray(); 26 | sampleStrings = mContext.getResources().getStringArray(R.array.exampleStrings); 27 | } 28 | 29 | @Override 30 | public int getCount() { 31 | return sampleStrings.length; 32 | } 33 | 34 | @Override 35 | public Object getItem(int position) { 36 | return null; 37 | } 38 | 39 | @Override 40 | public long getItemId(int position) { 41 | return 0; 42 | } 43 | 44 | @Override 45 | public View getView(int position, View convertView, ViewGroup parent) { 46 | final ViewHolder viewHolder; 47 | if (convertView == null) { 48 | convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list, parent, false); 49 | viewHolder = new ViewHolder(); 50 | viewHolder.expandableTextView = (ExpandableTextView) convertView.findViewById(R.id.etv); 51 | convertView.setTag(viewHolder); 52 | } else { 53 | viewHolder = (ViewHolder) convertView.getTag(); 54 | } 55 | viewHolder.expandableTextView.setText(sampleStrings[position], position); 56 | return convertView; 57 | } 58 | 59 | 60 | private static class ViewHolder{ 61 | ExpandableTextView expandableTextView; 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/expandabletextviewexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.expandabletextviewexample; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.TabLayout; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.app.FragmentManager; 7 | import android.support.v4.app.FragmentPagerAdapter; 8 | import android.support.v4.app.ListFragment; 9 | import android.support.v4.view.ViewPager; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import jaydenxiao.com.expandabletextview.ExpandableTextView; 18 | 19 | 20 | public class MainActivity extends AppCompatActivity { 21 | private ViewPager mViewPager; 22 | private TabLayout mTabLayout; 23 | private SectionsPagerAdapter mSectionsPagerAdapter; 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_main); 28 | initiView(); 29 | initData(); 30 | } 31 | 32 | private void initiView() { 33 | mViewPager= (ViewPager) findViewById(R.id.container); 34 | mTabLayout= (TabLayout) findViewById(R.id.tabs ); 35 | } 36 | 37 | private void initData() { 38 | mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); 39 | mViewPager = (ViewPager) findViewById(R.id.container); 40 | mViewPager.setAdapter(mSectionsPagerAdapter); 41 | 42 | mTabLayout = (TabLayout) findViewById(R.id.tabs); 43 | mTabLayout.setupWithViewPager(mViewPager); 44 | setupTabLayout(mTabLayout); 45 | } 46 | 47 | 48 | private void setupTabLayout(TabLayout tabLayout) { 49 | tabLayout.setTabMode(TabLayout.MODE_FIXED); 50 | tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); 51 | tabLayout.setupWithViewPager(mViewPager); 52 | } 53 | /** 54 | * viewpager适配器 55 | */ 56 | class SectionsPagerAdapter extends FragmentPagerAdapter { 57 | 58 | public SectionsPagerAdapter(FragmentManager fm) { 59 | super(fm); 60 | } 61 | 62 | @Override 63 | public Fragment getItem(int position) { 64 | if (position == 0) { 65 | return new FragmentListview(); 66 | } else { 67 | return new FragemntScrollview(); 68 | } 69 | } 70 | 71 | @Override 72 | public int getCount() { 73 | return 2; 74 | } 75 | 76 | @Override 77 | public CharSequence getPageTitle(int position) { 78 | switch (position) { 79 | case 0: 80 | return getString(R.string.situation1); 81 | case 1: 82 | return getString(R.string.situation2); 83 | } 84 | return null; 85 | } 86 | } 87 | 88 | /** 89 | 非列表情况fragment 90 | */ 91 | public static class FragemntScrollview extends Fragment { 92 | @Override 93 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 94 | Bundle savedInstanceState) { 95 | View rootView = inflater.inflate(R.layout.fra_scrollview, container, false); 96 | 97 | ExpandableTextView etvLeft = (ExpandableTextView) rootView.findViewById(R.id.etv_left); 98 | ExpandableTextView etvpRight = (ExpandableTextView) rootView.findViewById(R.id.etv_right); 99 | 100 | etvLeft.setOnExpandStateChangeListener(new ExpandableTextView.OnExpandStateChangeListener() { 101 | @Override 102 | public void onExpandStateChanged(TextView textView, boolean isExpanded) { 103 | Toast.makeText(getActivity(), isExpanded ? "展开" : "收起", Toast.LENGTH_SHORT).show(); 104 | } 105 | }); 106 | etvpRight.setOnExpandStateChangeListener(new ExpandableTextView.OnExpandStateChangeListener() { 107 | @Override 108 | public void onExpandStateChanged(TextView textView, boolean isExpanded) { 109 | Toast.makeText(getActivity(), isExpanded ? "展开" : "收起", Toast.LENGTH_SHORT).show(); 110 | } 111 | }); 112 | etvLeft.setText(getString(R.string.test_content)); 113 | etvpRight.setText(getString(R.string.test_content)); 114 | 115 | return rootView; 116 | } 117 | } 118 | /** 119 | 列表情况fragment 120 | */ 121 | public static class FragmentListview extends ListFragment { 122 | @Override 123 | public void onViewCreated(View view, Bundle savedInstanceState) { 124 | super.onViewCreated(view, savedInstanceState); 125 | ListAdapter adapter = new ListAdapter(getActivity()); 126 | setListAdapter(adapter); 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fra_listview.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fra_scrollview.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 17 | 18 | 26 | 40 | 48 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/ExpandableTextViewExample/f387755f12a2296a787754115b70d32a3bb0b832/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/ExpandableTextViewExample/f387755f12a2296a787754115b70d32a3bb0b832/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/ExpandableTextViewExample/f387755f12a2296a787754115b70d32a3bb0b832/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/ExpandableTextViewExample/f387755f12a2296a787754115b70d32a3bb0b832/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/ExpandableTextViewExample/f387755f12a2296a787754115b70d32a3bb0b832/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | "The story of a Chinese couple at Oxford University who first met eay who first met each other as deskmate in middle school and recently married each other has gone viral, ch other as deskmate in middle school and recently The story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recentlmarried each other has gone viral, southcn.com reported. how to build apps using Android's various APIs." 5 | "The story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recently married each other has gone viral, southcn.com reported., and a service independently performs work The story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recentlin the background." 6 | "The story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recently married each other has gone viral, southcn.com reported.ty in a maps app to show an address. This model provides multiple entry points for a single app and allows any app to behave as a user's "default" The story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recentlfor an action that other apps may invoke." 7 | "The story of a Chinese couple at Oxford University who first met each other as deskmate y who first met each other as deskmate in middle school and recently married each other has gone viral, in middle school and recently married each other has gone viral, southcn.com reported.ardware such as a camera. If necessary, you can also declare features your app requires so app markets such as Google Play Store do not allow installation on devices that do not support that feature." 8 | "The story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recently married each other has gone viral, southcn.com reported.s. For example, you can create different XML layout files for different screen sizes and the system determines which layout to apply based on the current device's screen size." 9 | "Android provides a rich apThe story of a Chinese couple at Oxford University who first met each other as dey who first met each other as deskmate in middle school and recently married each other has gone viral, skmate in middle school and recentlplication framework that allows you to build innovative apps and games for mobile devices in a Java language environment. The documents listed in the left navigation provide details about how to build apps using Android's various APIs." 10 | "Android provides a rich application ThThe story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recentle story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recently married each other has gone viral, southcn.com reported." 11 | "Android provides a rich application framework that The story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recentlallows you to build innovative apps and games for mobile devices in a Java language environment. The documents listed in the left navigation provide details about how to build apps using Android's various APIs." 12 | "The story of a Chinese couple at Oxford University who first met The story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recentleach other as deskmate in middle school and recently married each other has gone viral, southcn.com reported.uments listed in the left navigation provide details about how to build apps using Android's various APIs." 13 | "Android provides a rich application framework that allows you to build innovative apps and games for mobile devices in a Java language environment. The documents listed in the left navigation provide details about how to build apps using Android's various APIs." 14 | "test application The story" 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FA7C20 4 | #FA7C20 5 | #FA7C20 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ExpandableTextView 3 | 非列表情况 4 | 列表情况 5 | The story of a Chinese couple at Oxford University who first met each other as deskmate in middle school and recently married each other has gone viral, southcn.com reported. 6 | Zhou Si, the groom, and Chen Mengge, the bride, married each other on Aug 18 and will together go to Oxford University to pursue their doctoral degrees later this year. 7 | They were deskmate in middle school and then both were admitted by same high school but not in same class. 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/test/java/com/jaydenxiao/expandabletextview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.expandabletextview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /arts/expandabletextview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/ExpandableTextViewExample/f387755f12a2296a787754115b70d32a3bb0b832/arts/expandabletextview.gif -------------------------------------------------------------------------------- /arts/qrcode_for_gh_e90bfe968c01_430.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/ExpandableTextViewExample/f387755f12a2296a787754115b70d32a3bb0b832/arts/qrcode_for_gh_e90bfe968c01_430.jpg -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.0.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/ExpandableTextViewExample/f387755f12a2296a787754115b70d32a3bb0b832/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':ExpandableTextView' 2 | --------------------------------------------------------------------------------