├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── hongbing.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── faith │ │ └── ctv │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── faith │ │ │ └── ctv │ │ │ ├── MainActivity.java │ │ │ └── PicDescView.java │ └── res │ │ ├── layout │ │ └── activity_main.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 │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── faith │ └── ctv │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── guide1.png ├── guide2.png ├── guide3.png ├── guide4.png └── guide5.png └── settings.gradle /.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/dictionaries/hongbing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 26 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Android 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 69 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CustTextView-Android 2 | 自定义控价,用于显示描述内容和标题(适用场景:图片描述),动态计算高度和固定最大高度(文字超过4行的时候,默认显示4行文字);超过高度可以通过手势滑动拉大视图,文本单独滚动 3 | 4 | 5 | ### Screenshots 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.2" 6 | defaultConfig { 7 | applicationId "com.faith.ctv" 8 | minSdkVersion 14 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 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 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:24.2.1' 28 | testCompile 'junit:junit:4.12' 29 | } 30 | -------------------------------------------------------------------------------- /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 /Users/hongbing/Library/Android/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/faith/ctv/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.faith.ctv; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.faith.ctv", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/faith/ctv/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.faith.ctv; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Toast; 7 | 8 | public class MainActivity extends AppCompatActivity { 9 | 10 | private static final String TEST_TITLE = "自定义可滑动变高的View"; 11 | public static final String shortStr = "我是中国人,我是中国人的的阿大发撒发生中国人"; 12 | public static final String midStr = "我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人"; 13 | public static final String longStr = "我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人" + 14 | "的的阿大发撒发生我是中国人我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人," + 15 | "我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人," + 16 | "我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人"; 17 | public static final String longlongStr = "我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人" + 18 | "的的阿大发撒发生我是中国人我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人," + 19 | "我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人," + 20 | "我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人我是中国人的的阿大发撒发生;我是中国人,我是中" + 21 | "国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人\" +\n" + 22 | " \"的的阿大发撒发生我是中国人我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,\" +\n" + 23 | " \"我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,\" +\n" + 24 | " \"我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人我是中国人的的阿大发撒发生;我是中国人,我是中\" +\n" + 25 | " \"国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人"; 26 | 27 | public static final int CLK_TYPE_SHORT = 0; 28 | public static final int CLK_TYPE_MID = 1; 29 | public static final int CLK_TYPE_LONG = 2; 30 | public static final int CLK_TYPE_LONG_LONG = 3; 31 | 32 | private int mClkType = CLK_TYPE_SHORT; 33 | 34 | /** 35 | * 初始化布局 36 | */ 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_main); 41 | 42 | final PicDescView pdv = (PicDescView) findViewById(R.id.id_descLayout); 43 | pdv.setTextDesc(TEST_TITLE); 44 | toast("点击按钮切换底部文字内容哦"); 45 | 46 | findViewById(R.id.id_testHgtTv).setOnClickListener(new View.OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | 50 | switch (mClkType) { 51 | case CLK_TYPE_SHORT: 52 | pdv.setTextDesc(TEST_TITLE,shortStr); 53 | mClkType = CLK_TYPE_MID; 54 | break; 55 | case CLK_TYPE_MID: 56 | pdv.setTextDesc(TEST_TITLE,midStr); 57 | mClkType = CLK_TYPE_LONG; 58 | break; 59 | case CLK_TYPE_LONG: 60 | pdv.setTextDesc(TEST_TITLE,longStr); 61 | mClkType = CLK_TYPE_LONG_LONG; 62 | toast("向上滑动试试..."); 63 | break; 64 | case CLK_TYPE_LONG_LONG: 65 | pdv.setTextDesc(TEST_TITLE,longlongStr); 66 | mClkType = CLK_TYPE_SHORT; 67 | toast("向上滑动试试..."); 68 | break; 69 | } 70 | 71 | 72 | } 73 | }); 74 | } 75 | 76 | 77 | 78 | private void toast(String str){ 79 | Toast.makeText(this, str, Toast.LENGTH_SHORT).show(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/faith/ctv/PicDescView.java: -------------------------------------------------------------------------------- 1 | package com.faith.ctv; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.support.v4.view.MotionEventCompat; 6 | import android.text.Layout; 7 | import android.text.TextPaint; 8 | import android.text.TextUtils; 9 | import android.util.AttributeSet; 10 | import android.util.Log; 11 | import android.view.MotionEvent; 12 | import android.view.ViewGroup; 13 | import android.widget.LinearLayout; 14 | import android.widget.RelativeLayout; 15 | import android.widget.ScrollView; 16 | import android.widget.TextView; 17 | 18 | /** 19 | * 自定义可滑动的文本view 20 | * Created by hongbing on 16/9/22. 21 | */ 22 | public class PicDescView extends LinearLayout { 23 | 24 | private static final String TAG = PicDescView.class.getSimpleName(); 25 | private static final int FLING_MIN_DISTANCE = 6;// 移动最小距离 26 | // public static final String mDefaultStr = "我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人" + 27 | // "的的阿大发撒发生我是中国人我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人," + 28 | // "我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人," + 29 | // "我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人,我是中国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人我是中国人的的阿大发撒发生;我是中国人,我是中" + 30 | // "国人的的阿大发撒发生我是中国人的的阿大发撒发生我是中国人,我是中国人的的阿大发撒发生;我是中国人"; 31 | 32 | private ScrollView mScrollView; 33 | private RelativeLayout mRelLayout; 34 | private TextView mTitleTv; 35 | private TextView mDescTv; 36 | 37 | // 默认文字,也就是实际应该显示的文字 38 | private String mDefaultStr; 39 | // 屏幕高度 40 | private int mScreenH = 0; 41 | // 评论受限制的最大高度 42 | private int mViewLimitMaxH = 0; 43 | // 控价的最大高度 44 | private int mViewMaxH = 0; 45 | // 控件的最小高度 46 | private int mViewMinH = 0; 47 | private int mLastY = 0; 48 | // 是否超出限制 49 | private boolean mIsLimitUp, mIsLimitDown; 50 | private boolean mIsUp,mIsDown; 51 | // 是否可以全部显示 52 | private boolean mIsAll; 53 | private boolean mDescIsNull; 54 | // 最大化高度是否大于受限高度 55 | private boolean mIsMaxView; 56 | 57 | public PicDescView(Context context) { 58 | this(context, null, 0); 59 | } 60 | 61 | public PicDescView(Context context, AttributeSet attrs) { 62 | this(context, attrs, 0); 63 | } 64 | 65 | public PicDescView(Context context, AttributeSet attrs, int defStyleAttr) { 66 | super(context, attrs, defStyleAttr); 67 | init(); 68 | } 69 | 70 | private void init() { 71 | mScreenH = getScreenHgt(); 72 | mViewLimitMaxH = mScreenH / 2; 73 | } 74 | 75 | @Override 76 | protected void onFinishInflate() { 77 | super.onFinishInflate(); 78 | Log.d(TAG,"测试:onFinishInflate..."); 79 | 80 | mScrollView = (ScrollView)getChildAt(0); 81 | mRelLayout = (RelativeLayout) mScrollView.getChildAt(0); 82 | mTitleTv = (TextView) mRelLayout.getChildAt(0); 83 | mDescTv = (TextView) mRelLayout.getChildAt(1); 84 | mDefaultStr = mDescTv.getText().toString(); 85 | } 86 | 87 | /** 88 | * 获取屏幕高度 89 | */ 90 | private int getScreenHgt() { 91 | return getResources().getDisplayMetrics().heightPixels; 92 | } 93 | 94 | /** 95 | * dp转px 96 | */ 97 | private static int dip2px(Context context, float dpValue) { 98 | final float scale = context.getResources().getDisplayMetrics().density; 99 | return (int) (dpValue * scale + 0.5f); 100 | } 101 | 102 | public void setTextDesc(final String title) { 103 | setTextDesc(title,""); 104 | } 105 | 106 | /** 107 | * 获取标题高度 108 | * @return 109 | */ 110 | private int getTitleHgt(){ 111 | Layout layout = mTitleTv.getLayout(); 112 | int desired = layout.getLineTop(mTitleTv.getLineCount()); 113 | int padding = mTitleTv.getCompoundPaddingTop() + mTitleTv.getCompoundPaddingBottom(); 114 | return desired + padding; 115 | } 116 | 117 | /** 118 | * 动态计算控件的高度 119 | * @param vH 120 | */ 121 | private void caclViewHgt(int vH){ 122 | ViewGroup.LayoutParams params = getLayoutParams(); 123 | params.height = vH; 124 | setLayoutParams(params); 125 | } 126 | 127 | public void setTextDesc(final String title, final String desc) { 128 | post(new Runnable() { 129 | @Override 130 | public void run() { 131 | 132 | mTitleTv.setText(title); 133 | 134 | mDescIsNull = false; 135 | mDescTv.setVisibility(VISIBLE); 136 | // 其它控件/间距高度(titleH + paddingTop&paddingBtm) 137 | int otherH = getTitleHgt() + dip2px(getContext(), 16) + dip2px(getContext(),8); 138 | 139 | if (TextUtils.isEmpty(desc)) { 140 | mDescIsNull = true; 141 | mDescTv.setVisibility(GONE); 142 | caclViewHgt(otherH); 143 | return; 144 | } 145 | mDefaultStr = desc; 146 | // 需要加上desc控件距离上边的距离 147 | otherH += dip2px(getContext(), 7); 148 | TextPaint paint = mDescTv.getPaint(); 149 | // 一个字符的长度 150 | float oneTxtW = paint.measureText("中国人") / 3; 151 | // 描述文字控件每行的实际长度 152 | float rowW = mDescTv.getMeasuredWidth(); 153 | // 每行最多能显示的文字数 154 | int rowCount = (int) Math.ceil(rowW / oneTxtW - 1); 155 | // int rowCount = (int) Math.ceil(rowW / oneTxtW); 156 | 157 | mDescTv.setText(desc); 158 | mScrollView.fullScroll(ScrollView.FOCUS_UP); 159 | if (desc.length() <= rowCount * 4) { // 全部显示 160 | mIsAll = true; 161 | // 计算显示高度 162 | mViewMaxH = mViewMinH = mDescTv.getLineCount() * mDescTv.getLineHeight() + otherH; 163 | caclViewHgt(mViewMinH); 164 | } else { // 截取显示 165 | mIsAll = false; 166 | // 计算最小高度(固定4行H + otherH) 167 | mViewMinH = mDescTv.getLineHeight() * 4 + otherH; 168 | caclViewHgt(mViewMinH); 169 | 170 | // 默认最多保持四行 171 | // int endIndex = 0; 172 | // if (mDefaultStr.length() > rowCount * 4 + 2) { 173 | // endIndex = rowCount * 4 + 4;//额外补字 174 | // } else { 175 | // endIndex = rowCount * 4; 176 | // } 177 | // 计算最小化时的显示内容 178 | // mLimitStr = desc.subSequence(0, endIndex).toString(); 179 | // mDescTv.setText(mLimitStr); 180 | // 计算总高度 = 最大化时显示的行数 * 行高 + 其它控件高度 -->默认在计算出来的行数上+1,不然会导致高度计算有问题,造成一行内容显示不全的问题 181 | mViewMaxH = (mDefaultStr.length() / rowCount + 1) * mDescTv.getLineHeight() + otherH; 182 | if(mViewMaxH > mViewLimitMaxH){ // 最大化高度大于受限高度 183 | mViewMaxH = mViewLimitMaxH; 184 | mIsMaxView = true; 185 | } 186 | // TDNewsUtil.dbgLog("测量:最大化高度 > 受限高度 = " + mIsMaxView); 187 | } 188 | 189 | // TDNewsUtil.dbgLog("测量:控件显示高度 = " + getHeight() + 190 | // "-->屏幕高度 = " + mScreenH + 191 | // "-->最大化受限高度 = " + mViewLimitMaxH + 192 | // "-->描述文字的行高 = " + mDescTv.getLineHeight() + 193 | // "-->计算出来的控件高度 = " + mViewMinH + 194 | // "-->最大化时控件高度 = " + mViewMaxH + 195 | // "-->每行最多显示字符数 = " + rowCount + "-->是否全部显示 = " + mIsAll); 196 | } 197 | }); 198 | } 199 | 200 | @Override 201 | protected void onDraw(Canvas canvas) { 202 | super.onDraw(canvas); 203 | } 204 | 205 | // @Override 206 | // protected void onLayout(boolean changed, int l, int t, int r, int b) { 207 | // super.onLayout(changed, l, t, r, b); 208 | //// TDNewsUtil.dbgLog("测量:onlayout" + "-->l = " + l + "-->t = " + t + "-->r = " + r + "-->b = " + b); 209 | // if (mIsInit) { 210 | //// mDescTv.layout(mDescTv.getLeft(), mDescTv.getTop(), mDescTv.getRight(), mDescTv.getBottom()); 211 | //// mScrollView.layout(mScrollView.getLeft(), mScrollView.getTop(), mScrollView.getRight(), mScrollView.getBottom()); 212 | // mRelLayout.layout(mRelLayout.getLeft(), mRelLayout.getTop(), mRelLayout.getRight(), mRelLayout.getBottom()); 213 | // mIsInit = false; 214 | // } else { 215 | //// mDescTv.layout(mDescTv.getLeft(), mDescTv.getTop(), mDescTv.getRight(), b); 216 | //// mScrollView.layout(mScrollView.getLeft(), mScrollView.getTop(), mScrollView.getRight(), b); 217 | // mRelLayout.layout(mRelLayout.getLeft(), mRelLayout.getTop(), mRelLayout.getRight(), b); 218 | // } 219 | // } 220 | 221 | private boolean mSvIsTop; 222 | private int mDownY; 223 | private boolean isDisTouch; 224 | 225 | @Override 226 | public boolean dispatchTouchEvent(MotionEvent event) { 227 | 228 | if(mIsAll || mDescIsNull) return false; 229 | 230 | if (mIsMaxView && mIsLimitUp) { // 最最大化 231 | if (mScrollView.getScrollY() > 0) { // scrollview没有到顶部,先让它到顶部 232 | mSvIsTop = false; 233 | mScrollView.requestDisallowInterceptTouchEvent(false); 234 | // requestDisallowInterceptTouchEvent(true); 235 | 236 | if (event.getAction() == MotionEvent.ACTION_UP) { 237 | mDownY = 0; 238 | mLastY = 0; 239 | // mIsUp = false; 240 | // mIsDown = false; 241 | } else if (event.getAction() == MotionEvent.ACTION_MOVE) { 242 | 243 | isDisTouch = true; 244 | 245 | // mDownY = (int) event.getY(); 246 | // mLastY = (int) event.getY(); 247 | } 248 | 249 | Log.d(TAG,"测试:被拦截"); 250 | return super.dispatchTouchEvent(event); // 继续往下传递 251 | }else{ // 顶部 252 | mSvIsTop = true; 253 | // mScrollView.setEnabled(false); 254 | // 从拦截到释放的过程,需要重新计算按下值 255 | if(isDisTouch){ 256 | mDownY = (int) event.getY(); 257 | mLastY = (int) event.getY(); 258 | isDisTouch = false; 259 | } 260 | 261 | mScrollView.requestDisallowInterceptTouchEvent(true); 262 | // requestDisallowInterceptTouchEvent(false); 263 | } 264 | } 265 | 266 | int y = (int) event.getY(); 267 | 268 | // switch (event.getAction() & event.getActionMasked()) { 269 | switch (MotionEventCompat.getActionMasked(event)) { 270 | case MotionEvent.ACTION_DOWN: 271 | mDownY = y; 272 | mLastY = y; 273 | break; 274 | case MotionEvent.ACTION_UP: 275 | mScrollView.requestDisallowInterceptTouchEvent(true); 276 | mDownY = 0; 277 | mLastY = 0; 278 | mIsLimitUp = false; 279 | mIsLimitDown = false; 280 | mIsUp = false; 281 | mIsDown = false; 282 | mSvIsTop = false; 283 | // scrollview是停留在顶部的,那么对应的变量需要置成true 284 | if(mScrollView.getScrollY() <= 0){ 285 | mSvIsTop = true; 286 | } 287 | // 设置字数超出省略号 288 | // if (getHeight() <= mViewMinH) { 289 | // mDescTv.setText(mLimitStr); 290 | // } 291 | break; 292 | case MotionEvent.ACTION_MOVE: 293 | int dy = y - mLastY; 294 | if(!mIsLimitUp){ // 没有到达最最大化,不能使用scrollview的事件 295 | mScrollView.requestDisallowInterceptTouchEvent(true); 296 | if(mScrollView.getScrollY() <= 0){ 297 | mSvIsTop = true; 298 | } 299 | } 300 | 301 | // 用作方位判断 302 | if (y < mDownY && Math.abs(y - mDownY) > FLING_MIN_DISTANCE) { // 向上滑动 303 | if(mIsDown){ // 先向下,然后变成向上 304 | mLastY = y; 305 | mIsDown = false; 306 | Log.d(TAG,"测试:-->先向下,然后变成向上"); 307 | } 308 | mIsUp = true; 309 | } else if (y > mDownY && Math.abs(y - mDownY) > FLING_MIN_DISTANCE) { // 向下滑动 310 | if(mIsUp){ // 先向下,然后变成向上 311 | Log.d(TAG,"测试:-->先向上,然后变成向下"); 312 | mLastY = y; 313 | mIsUp = false; 314 | } 315 | mIsDown = true; 316 | } 317 | 318 | Log.d(TAG,"测试:-->mIsUp = " + mIsUp + "-->mIsDown = " + mIsDown + "-->y = " + y + "-->mDownY = " + mDownY); 319 | 320 | if (mIsUp) { // 向上滑动 321 | 322 | if (getHeight() >= mViewMaxH && mIsMaxView) { 323 | // 屏蔽自己,scroolview接收 324 | mScrollView.requestDisallowInterceptTouchEvent(false); 325 | return super.dispatchTouchEvent(event); // 继续往下传递 326 | } 327 | 328 | int upCha = getHeight() + Math.abs(dy); 329 | Log.d(TAG,"测试:upCha = " + upCha + "-->mViewMaxH = " + mViewMaxH); 330 | mIsLimitUp = false; 331 | if (upCha >= mViewMaxH) { 332 | mIsLimitUp = true; 333 | caclViewHgt(mViewMaxH); 334 | Log.d(TAG,"测试:超过最大高度限制了...mIsMaxView = " + mIsMaxView); 335 | 336 | // if (mIsMaxView) { 337 | // // 屏蔽自己,scroolview接收 338 | // mScrollView.requestDisallowInterceptTouchEvent(false); 339 | // return super.dispatchTouchEvent(event); // 继续往下传递 340 | // }else{ 341 | //// mScrollView.fullScroll(ScrollView.FOCUS_UP); 342 | // } 343 | return false; 344 | } 345 | if(!mIsLimitUp) 346 | caclViewHgt(upCha); 347 | // layout(getLeft(),getTop()+dy,getRight(),getBottom()); 348 | } else if (mIsDown) { // 向下滑动 349 | Log.d(TAG,"测试:手势向下-->mScrollView.getScrollY() = " + mScrollView.getScrollY() + "-->mSvIsTop = " + mSvIsTop); 350 | mScrollView.fullScroll(ScrollView.FOCUS_UP); 351 | if (mSvIsTop) { // 下拉控件的时候,scrollview必须是在顶部,不然就是下拉scroolview的内容了 352 | // 将最大限制变量置成false,防止反复上下拖动导致的bug 353 | if (getHeight() < mViewMaxH) { 354 | mIsLimitUp = false; 355 | } 356 | 357 | int downCha = getHeight() - dy; 358 | if (downCha <= mViewMinH) { 359 | mIsLimitDown = true; 360 | caclViewHgt(mViewMinH); 361 | // TDNewsUtil.dbgLog("测量:低于最小高度限制了..." + mViewMinH); 362 | return false; 363 | } 364 | 365 | caclViewHgt(downCha); 366 | // layout(getLeft(), getTop() + dy, getRight(), getBottom()); 367 | } 368 | } 369 | 370 | // 更新按下的点 371 | // mDownY = y; 372 | break; 373 | } 374 | 375 | return super.dispatchTouchEvent(event); 376 | } 377 | 378 | } 379 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |