├── base ├── consumer-rules.pro ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── ic_back.png │ │ │ ├── ic_more.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap │ │ │ │ ├── ic_back.png │ │ │ │ └── ic_base_more.png │ │ │ └── layout │ │ │ │ ├── activity_base_tabbar.xml │ │ │ │ └── activity_base_titlebar.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── baymax │ │ │ └── base │ │ │ ├── activity │ │ │ ├── BaseActivity.java │ │ │ ├── BaseTabBarActivity.java │ │ │ └── BaseTitleBarActivity.java │ │ │ └── fragment │ │ │ ├── BaseFragment.java │ │ │ └── LazyLoadFragment.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── baymax │ │ │ └── base │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── baymax │ │ └── base │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── item_menu_selected.xml │ │ │ │ ├── fragment_layout_round.xml │ │ │ │ ├── fragment_layout_menu.xml │ │ │ │ ├── fragment_layout_expand.xml │ │ │ │ ├── fragment_layout_ratitngstar.xml │ │ │ │ └── fragment_layout_arrow.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── baymax │ │ │ │ └── widgetlibrary │ │ │ │ ├── fragment │ │ │ │ ├── MenuLayoutFragment.java │ │ │ │ ├── ArrowLayoutFragment.java │ │ │ │ ├── RoundLayoutFragment.java │ │ │ │ ├── RatingStarViewFragment.java │ │ │ │ └── ExpandLayoutFragment.java │ │ │ │ └── activity │ │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── baymax │ │ │ └── widgetlibrary │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── baymax │ │ └── widgetlibrary │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── widget ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ └── attrs.xml │ │ │ ├── mipmap │ │ │ │ └── ic_more.png │ │ │ ├── drawable │ │ │ │ ├── splitter_less.png │ │ │ │ └── splitter_more.png │ │ │ └── layout │ │ │ │ ├── layout_expand.xml │ │ │ │ └── layout_menu_item.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── baymax │ │ │ └── widget │ │ │ ├── ratingstar │ │ │ ├── VertexF.java │ │ │ ├── StarModel.java │ │ │ └── RatingStarView.java │ │ │ ├── RoundButton.java │ │ │ ├── ArrowRelativeLayout.java │ │ │ ├── MenuLayout.java │ │ │ └── ExpandLayout.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── baymax │ │ │ └── widget │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── baymax │ │ └── widget │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── vcs.xml ├── misc.xml ├── runConfigurations.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── codeStyles │ └── Project.xml └── dbnavigator.xml ├── .gitignore ├── gradle.properties ├── gradlew.bat └── gradlew /base/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /base/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /widget/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /widget/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':widget', ':base' 2 | rootProject.name='WidgetLibrary' 3 | -------------------------------------------------------------------------------- /base/src/main/res/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/base/src/main/res/ic_back.png -------------------------------------------------------------------------------- /base/src/main/res/ic_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/base/src/main/res/ic_more.png -------------------------------------------------------------------------------- /base/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | base 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | WidgetLibrary 3 | 4 | -------------------------------------------------------------------------------- /widget/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | widget 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /base/src/main/res/mipmap/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/base/src/main/res/mipmap/ic_back.png -------------------------------------------------------------------------------- /widget/src/main/res/mipmap/ic_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/widget/src/main/res/mipmap/ic_more.png -------------------------------------------------------------------------------- /base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /base/src/main/res/mipmap/ic_base_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/base/src/main/res/mipmap/ic_base_more.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /widget/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /widget/src/main/res/drawable/splitter_less.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/widget/src/main/res/drawable/splitter_less.png -------------------------------------------------------------------------------- /widget/src/main/res/drawable/splitter_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/widget/src/main/res/drawable/splitter_more.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukanggui/WidgetLibrary/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /base/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Sep 26 09:51:00 CST 2019 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-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /base/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /widget/src/main/java/com/baymax/widget/ratingstar/VertexF.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widget.ratingstar; 2 | 3 | /** 4 | * Created by hxw on 2017-04-23. 5 | * 拐点(顶点)坐标表示实体类 6 | */ 7 | public class VertexF { 8 | public VertexF() { 9 | } 10 | 11 | public VertexF(float x, float y) { 12 | this.x = x; 13 | this.y = y; 14 | } 15 | 16 | public float x; 17 | public float y; 18 | 19 | public VertexF next; 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /base/src/test/java/com/baymax/base/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.baymax.base; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /widget/src/test/java/com/baymax/widget/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widget; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/test/java/com/baymax/widgetlibrary/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widgetlibrary; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/baymax/widgetlibrary/fragment/MenuLayoutFragment.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widgetlibrary.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import com.baymax.base.fragment.BaseFragment; 7 | import com.baymax.widgetlibrary.R; 8 | 9 | /** 10 | * @author Baymax 11 | * @date 2019-11-17 12 | * 描述:菜单布局演示Fragment 13 | */ 14 | public class MenuLayoutFragment extends BaseFragment { 15 | @Override 16 | public int getLayoutResId() { 17 | return R.layout.fragment_layout_menu; 18 | } 19 | 20 | @Override 21 | public void loadData() { 22 | 23 | } 24 | 25 | @Override 26 | public void initView(View view, Bundle savedInstanceState) { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/baymax/widgetlibrary/fragment/ArrowLayoutFragment.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widgetlibrary.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import com.baymax.base.fragment.BaseFragment; 7 | import com.baymax.widgetlibrary.R; 8 | 9 | /** 10 | * @author Baymax 11 | * @date 2019-11-17 12 | * 描述:三角箭头布局演示Fragment 13 | */ 14 | public class ArrowLayoutFragment extends BaseFragment { 15 | @Override 16 | public int getLayoutResId() { 17 | return R.layout.fragment_layout_arrow; 18 | } 19 | 20 | @Override 21 | public void loadData() { 22 | 23 | } 24 | 25 | @Override 26 | public void initView(View view, Bundle savedInstanceState) { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/baymax/widgetlibrary/fragment/RoundLayoutFragment.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widgetlibrary.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import com.baymax.base.fragment.BaseFragment; 7 | import com.baymax.widgetlibrary.R; 8 | 9 | /** 10 | * @author Baymax 11 | * @date 2019-11-17 12 | * 描述:圆角布局演示Fragment 13 | */ 14 | public class RoundLayoutFragment extends BaseFragment { 15 | @Override 16 | public int getLayoutResId() { 17 | return R.layout.fragment_layout_round; 18 | } 19 | 20 | @Override 21 | public void loadData() { 22 | 23 | } 24 | 25 | @Override 26 | public void initView(View view, Bundle savedInstanceState) { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/baymax/widgetlibrary/fragment/RatingStarViewFragment.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widgetlibrary.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import com.baymax.base.fragment.BaseFragment; 7 | import com.baymax.widgetlibrary.R; 8 | 9 | /** 10 | * @author oukanggui 11 | * @date 2019/11/18 12 | * 描述:星星评分演示Fragment 13 | */ 14 | public class RatingStarViewFragment extends BaseFragment { 15 | @Override 16 | public int getLayoutResId() { 17 | return R.layout.fragment_layout_ratitngstar; 18 | } 19 | 20 | @Override 21 | public void loadData() { 22 | 23 | } 24 | 25 | @Override 26 | public void initView(View view, Bundle savedInstanceState) { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_menu_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 16 | 21 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /base/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /widget/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /base/src/androidTest/java/com/baymax/base/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.baymax.base; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.demo.base.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /widget/src/androidTest/java/com/baymax/widget/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widget; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.baymax.widget.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/baymax/widgetlibrary/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widgetlibrary; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.baymax.widgetlibrary", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | -------------------------------------------------------------------------------- /widget/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 29 5 | 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 29 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles 'consumer-rules.pro' 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | 29 | implementation 'androidx.appcompat:appcompat:1.0.2' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'androidx.test:runner:1.1.1' 32 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 33 | } 34 | -------------------------------------------------------------------------------- /base/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 29 5 | 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 29 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles 'consumer-rules.pro' 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | 29 | implementation 'androidx.appcompat:appcompat:1.0.2' 30 | implementation "com.google.android.material:material:1.0.0" 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'androidx.test:runner:1.1.1' 33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 34 | } 35 | -------------------------------------------------------------------------------- /base/src/main/res/layout/activity_base_tabbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | defaultConfig { 6 | applicationId "com.baymax.widgetlibrary" 7 | minSdkVersion 15 8 | targetSdkVersion 29 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'androidx.appcompat:appcompat:1.0.2' 24 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'androidx.test:runner:1.1.1' 27 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 28 | implementation project(path: ':widget') 29 | implementation project(path: ':base') 30 | 31 | } 32 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /base/src/main/java/com/baymax/base/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.baymax.base.activity; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.Nullable; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | /** 9 | * @author oukanggui 10 | * @date 2018/11/11 11 | * 描述 BaseActivity基类,主要功能如下: 12 | * 1、支持显示以及设置加载中、数据为空、加载出错重试等布局 13 | * 2、View以及数据初始化方法回调: 14 | * @see #initView(Bundle) 15 | * @see #initData() 16 | */ 17 | 18 | public abstract class BaseActivity extends AppCompatActivity { 19 | 20 | @Override 21 | protected void onCreate(@Nullable Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(getLayoutResId()); 24 | initView(savedInstanceState); 25 | initData(); 26 | } 27 | 28 | @Override 29 | protected void onDestroy() { 30 | super.onDestroy(); 31 | } 32 | 33 | /** 34 | * 子Activity重写该方法初始化布局View 35 | */ 36 | protected abstract void initView(Bundle savedInstanceState); 37 | 38 | /** 39 | * 子Activity重写该方法初始化数据 40 | */ 41 | protected abstract void initData(); 42 | 43 | /** 44 | * 子Activity重写该方法提供布局 45 | */ 46 | protected abstract int getLayoutResId(); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_layout_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 22 | 23 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_layout_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | 20 | 28 | 36 | 37 | -------------------------------------------------------------------------------- /base/src/main/res/layout/activity_base_titlebar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 21 | 22 | 29 | 30 | 38 | 39 | 40 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /widget/src/main/res/layout/layout_expand.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 15 | 16 | 23 | 24 | 32 | 33 | 41 | 42 | 43 | 44 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /base/src/main/java/com/baymax/base/fragment/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.baymax.base.fragment; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import androidx.annotation.Nullable; 11 | 12 | 13 | /** 14 | * @author oukanggui 15 | * @date 2018/11/14 16 | * 描述 BaseFragment基类,主要功能如下: 17 | * 1、支持显示以及设置加载中、数据为空、加载出错重试等布局 18 | * 2、View布局初始化以及Fragment第一次可见时数据加载等 19 | */ 20 | 21 | public abstract class BaseFragment extends LazyLoadFragment { 22 | private View rootView; 23 | protected Activity mActivity; 24 | 25 | @Override 26 | public void onAttach(Context context) { 27 | mActivity = (Activity) context; 28 | super.onAttach(context); 29 | } 30 | 31 | @Nullable 32 | @Override 33 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 34 | if (rootView == null) { 35 | rootView = inflater.inflate(getLayoutResId(), container, false); 36 | initView(rootView, savedInstanceState); 37 | initData(); 38 | } else { 39 | ViewGroup parent = (ViewGroup) rootView.getParent(); 40 | if (parent != null) { 41 | parent.removeView(rootView); 42 | } 43 | } 44 | return rootView; 45 | } 46 | 47 | /** 48 | * StateView的根布局,默认是整个界面,如果需要变换可以重写此方法 49 | */ 50 | public View getStateViewRoot() { 51 | return rootView; 52 | } 53 | 54 | /** 55 | * 获取Fragment资源布局ID,子类Fragment重写该方法返回Fragment展示布局的资源id 56 | */ 57 | public abstract int getLayoutResId(); 58 | 59 | /** 60 | * 加载数据,在Fragment第一次可见时回调,子类Fragment实现该方法进行数据加载(网络、数据库等) 61 | */ 62 | public abstract void loadData(); 63 | 64 | /** 65 | * 初始化UI,子类Fragment实现该方法进行UI初始化相关工作 66 | */ 67 | public abstract void initView(View view, Bundle savedInstanceState); 68 | 69 | /** 70 | * 初始化数据,子类Fragment可以选择性实现该方法进行数据初始化相关工作 71 | */ 72 | protected void initData() { 73 | } 74 | 75 | 76 | @Override 77 | protected void onFragmentFirstVisible() { 78 | //在Fragment第一次可见时,进行数据加载+ 79 | loadData(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /widget/src/main/res/layout/layout_menu_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 18 | 19 | 29 | 30 | 39 | 49 | 50 | 56 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/baymax/widgetlibrary/fragment/ExpandLayoutFragment.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widgetlibrary.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.Toast; 6 | 7 | import androidx.core.content.ContextCompat; 8 | 9 | import com.baymax.base.fragment.BaseFragment; 10 | import com.baymax.widget.ExpandLayout; 11 | import com.baymax.widgetlibrary.R; 12 | 13 | /** 14 | * @author Baymax 15 | * @date 2019-11-17 16 | * 描述:ExpandLayout的演示Fragment 17 | */ 18 | public class ExpandLayoutFragment extends BaseFragment { 19 | private ExpandLayout mExpandLayoutDefault, mExpandLayoutIcon, mExpandLayoutText; 20 | 21 | @Override 22 | public int getLayoutResId() { 23 | return R.layout.fragment_layout_expand; 24 | } 25 | 26 | @Override 27 | public void loadData() { 28 | 29 | } 30 | 31 | @Override 32 | public void initView(View view, Bundle savedInstanceState) { 33 | mExpandLayoutDefault = view.findViewById(R.id.my_expand_default); 34 | mExpandLayoutIcon = view.findViewById(R.id.my_expand_icon); 35 | mExpandLayoutText = view.findViewById(R.id.my_expand_text); 36 | //要展示的文字内容 37 | String contentStr = "我是正常的全中文文字,可以点击我展开查看更多或收起,我是图标+文字的默认模式"; 38 | mExpandLayoutDefault.setContent(contentStr, new ExpandLayout.OnExpandStateChangeListener() { 39 | @Override 40 | public void onExpand() { 41 | mExpandLayoutDefault.setContentTextColor(ContextCompat.getColor(mActivity, android.R.color.holo_blue_light)); 42 | } 43 | 44 | @Override 45 | public void onCollapse() { 46 | mExpandLayoutDefault.setExpandTextColor(ContextCompat.getColor(mActivity, android.R.color.holo_red_dark)); 47 | } 48 | }); 49 | String contentStr1 = "我是图标样式,Android、Android、Android,中间有换行符\n\n\nAndroid、你好AndroidAndroid、哈哈Android、Android、Android你好"; 50 | mExpandLayoutIcon.setContent(contentStr1); 51 | String contentStr2 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"; 52 | mExpandLayoutText.setContent(contentStr2, new ExpandLayout.OnExpandStateChangeListener() { 53 | @Override 54 | public void onExpand() { 55 | Toast.makeText(mActivity, "onExpand", Toast.LENGTH_SHORT).show(); 56 | } 57 | 58 | @Override 59 | public void onCollapse() { 60 | Toast.makeText(mActivity, "onCollapse", Toast.LENGTH_SHORT).show(); 61 | } 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_layout_expand.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 28 | 44 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/baymax/widgetlibrary/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widgetlibrary.activity; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.widget.PopupWindow; 8 | 9 | import androidx.fragment.app.Fragment; 10 | 11 | import com.baymax.base.activity.BaseTabBarActivity; 12 | import com.baymax.widgetlibrary.R; 13 | import com.baymax.widgetlibrary.fragment.ArrowLayoutFragment; 14 | import com.baymax.widgetlibrary.fragment.ExpandLayoutFragment; 15 | import com.baymax.widgetlibrary.fragment.MenuLayoutFragment; 16 | import com.baymax.widgetlibrary.fragment.RatingStarViewFragment; 17 | import com.baymax.widgetlibrary.fragment.RoundLayoutFragment; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * 测试主Activity界面 24 | */ 25 | public class MainActivity extends BaseTabBarActivity { 26 | 27 | @Override 28 | protected void initView(Bundle savedInstanceState) { 29 | super.initView(savedInstanceState); 30 | setTitle("WidgetLibrary"); 31 | setTitleTextColor(Color.WHITE); 32 | showBackView(false); 33 | showRightView(true); 34 | } 35 | 36 | @Override 37 | protected List createFragments() { 38 | List fragments = new ArrayList<>(); 39 | fragments.add(new RatingStarViewFragment()); 40 | fragments.add(new ExpandLayoutFragment()); 41 | fragments.add(new RoundLayoutFragment()); 42 | fragments.add(new MenuLayoutFragment()); 43 | fragments.add(new ArrowLayoutFragment()); 44 | return fragments; 45 | } 46 | 47 | @Override 48 | protected List createPagerTitles() { 49 | List titles = new ArrayList<>(); 50 | titles.add("RatingStarView"); 51 | titles.add("ExpandLayout"); 52 | titles.add("RoundLayout"); 53 | titles.add("MenuLayout"); 54 | titles.add("ArrowLayout"); 55 | return titles; 56 | } 57 | 58 | @Override 59 | protected void onRightViewClicked(View view) { 60 | super.onRightViewClicked(view); 61 | PopupWindow popupWindow = makePopupWindow(); 62 | int[] xy = new int[2]; 63 | view.getLocationOnScreen(xy); 64 | //popupWindow.showAtLocation(view, Gravity.RIGHT | Gravity.TOP, -xy[0] / 2, xy[1] + button.getWidth()); 65 | popupWindow.showAsDropDown(view,20, 20); 66 | } 67 | 68 | /** 69 | * 创建PopupWindow 70 | */ 71 | private PopupWindow makePopupWindow() 72 | { 73 | PopupWindow popupWindow = new PopupWindow(this); 74 | 75 | View contentView = LayoutInflater.from(this).inflate(R.layout.item_menu_selected, null); 76 | popupWindow.setContentView(contentView); 77 | popupWindow.setBackgroundDrawable(null); 78 | //设置PopupWindow可获得焦点 79 | popupWindow.setFocusable(true); 80 | //设置PopupWindow可触摸 81 | popupWindow.setTouchable(true); 82 | //设置非PopupWindow区域可触摸 83 | popupWindow.setOutsideTouchable(true); 84 | return popupWindow; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /base/src/main/java/com/baymax/base/activity/BaseTabBarActivity.java: -------------------------------------------------------------------------------- 1 | package com.baymax.base.activity; 2 | 3 | import android.os.Bundle; 4 | import android.view.ViewGroup; 5 | 6 | 7 | import androidx.fragment.app.Fragment; 8 | import androidx.fragment.app.FragmentManager; 9 | import androidx.fragment.app.FragmentPagerAdapter; 10 | import androidx.viewpager.widget.ViewPager; 11 | 12 | import com.baymax.base.R; 13 | import com.google.android.material.tabs.TabLayout; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * @author oukanggui 19 | * @date 2018/11/19 20 | * 描述 导航栏TabBarActivity 21 | */ 22 | 23 | public abstract class BaseTabBarActivity extends BaseTitleBarActivity { 24 | protected ViewPager mViewPager; 25 | protected TabLayout mTabLayout; 26 | protected FragmentPagerAdapter mPagerAdapter; 27 | 28 | @Override 29 | protected void initView(Bundle savedInstanceState) { 30 | mViewPager = findViewById(R.id.base_viewpager); 31 | mTabLayout = findViewById(R.id.base_tablayout); 32 | } 33 | 34 | @Override 35 | protected void initData() { 36 | mPagerAdapter = new CommonFragmentPagerAdapter(getSupportFragmentManager(),createFragments(),createPagerTitles()); 37 | mViewPager.setAdapter(mPagerAdapter); 38 | if (createFragments() != null) { 39 | mViewPager.setOffscreenPageLimit(createFragments().size()); 40 | } 41 | mTabLayout.setupWithViewPager(mViewPager); 42 | } 43 | 44 | @Override 45 | protected int getLayoutResId() { 46 | return R.layout.activity_base_tabbar; 47 | } 48 | 49 | /** 50 | * 供子类重写创建显示的Fragment 51 | * 52 | * */ 53 | protected abstract List createFragments(); 54 | 55 | /** 56 | * 供子类重写创建显示的Title 57 | * 58 | * */ 59 | protected abstract List createPagerTitles(); 60 | 61 | private static class CommonFragmentPagerAdapter extends FragmentPagerAdapter { 62 | private List fragments; 63 | private List titles; 64 | 65 | public CommonFragmentPagerAdapter(FragmentManager fm, List fragments, List titles) { 66 | super(fm); 67 | this.fragments = fragments; 68 | this.titles = titles; 69 | } 70 | 71 | @Override 72 | public int getCount() { 73 | return titles.size(); 74 | } 75 | 76 | @Override 77 | public Fragment getItem(int position) { 78 | return fragments == null ? null : fragments.get(position); 79 | } 80 | 81 | @Override 82 | public Object instantiateItem(ViewGroup container, int position) { 83 | return super.instantiateItem(container, position); 84 | } 85 | 86 | @Override 87 | public void destroyItem(ViewGroup container, int position, Object object) { 88 | super.destroyItem(container, position, object); 89 | } 90 | 91 | @Override 92 | public CharSequence getPageTitle(int position) { 93 | return titles == null ? null : titles.get(position); 94 | } 95 | 96 | @Override 97 | public long getItemId(int position) { 98 | return position; 99 | } 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /base/src/main/java/com/baymax/base/fragment/LazyLoadFragment.java: -------------------------------------------------------------------------------- 1 | package com.baymax.base.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.View; 6 | 7 | import androidx.annotation.Nullable; 8 | import androidx.fragment.app.Fragment; 9 | 10 | /** 11 | * @author oukanggui 12 | * @date 2018/11/14 13 | * 描述 懒加载Fragment 14 | * 在使用了ViewPager + Fragment的情况下,Fragment的生命周期因ViewPager的缓存机制而失去了具体的意义 15 | * 该抽象类自定义新的回调方法,当Fragment可见状态改变时或Fragment第一次可见时会回调的方法 16 | * @see #onFragmentVisibleChange(boolean) 17 | * @see #onFragmentFirstVisible() 18 | */ 19 | 20 | public abstract class LazyLoadFragment extends Fragment { 21 | 22 | protected String TAG = getClass().getSimpleName(); 23 | /** 24 | * 标识是否是第一次进来 25 | */ 26 | private boolean mIsFirstEnter = true; 27 | /** 28 | * 页面布局根View 29 | */ 30 | private View mRootView; 31 | 32 | /** 33 | * 该方法的回调时机: 34 | * 1)在Fragment创建时会先回调一次(isVisibleToUser=false),此时Fragment还不可见,UI可能还没有初始化 35 | * 2)Fragment创建完可见时,该方法会再次回调一次(isVisibleToUser=true) 36 | * 3)Fragment从可见-->不可见时,该方法也会被回调(isVisibleToUser=false) 37 | * 总结:setUserVisibleHint()除了在Fragment的可见状态发生变化时会被回调外,在new Fragment()时也会被回调 38 | * 如果我们需要在 Fragment 可见与不可见时干点事,用这个的话就会有多余的回调了,就需要重新封装一个 39 | */ 40 | @Override 41 | public void setUserVisibleHint(boolean isVisibleToUser) { 42 | Log.i(TAG, "setUserVisibleHint = " + isVisibleToUser); 43 | super.setUserVisibleHint(isVisibleToUser); 44 | //该方法有可能在fragment的生命周期外被调用 45 | //保证在View初始化的时候在进行处理 46 | if (mRootView == null) { 47 | return; 48 | } 49 | if (mIsFirstEnter && isVisibleToUser) { 50 | //第一次进来且可见的时候,回调Fragment首次可见 51 | onFragmentFirstVisible(); 52 | mIsFirstEnter = false; 53 | } 54 | //过滤在Fragment创建时回调该方法的情况 55 | if (!mIsFirstEnter) { 56 | //如果不是第一次进来或者已处理完第一次可见(保证onFragmentFirstVisible()先回调),回调Fragment可见状态 57 | onFragmentVisibleChange(isVisibleToUser); 58 | } 59 | } 60 | 61 | @Override 62 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 63 | Log.i(TAG, "onViewCreated"); 64 | //如果setUserVisibleHint()在rootView创建前调用时(此时setUserVisibleHint没有回调处理),那么 65 | //就等到rootView创建完后才回调onFragmentFirstVisible()以及onFragmentVisibleChange(true) 66 | //保证这两个方法的回调发生在rootView创建完成之后,以便支持ui操作 67 | if (mRootView == null) { 68 | mRootView = view; 69 | if (getUserVisibleHint()) { 70 | if (mIsFirstEnter) { 71 | onFragmentFirstVisible(); 72 | mIsFirstEnter = false; 73 | } 74 | onFragmentVisibleChange(true); 75 | } 76 | } 77 | super.onViewCreated(mRootView, savedInstanceState); 78 | } 79 | 80 | 81 | /** 82 | * 当Fragment可见状态变化时回调该方法: 83 | * 1)去除setUserVisibleHint()多余的回调场景,保证只有当Fragment可见状态发生变化时才回调 84 | * 2)回调时机在view创建完后,支持ui操作,解决在setUserVisibleHint()里进行ui操作有可能报null异常的问题 85 | * 可以在该方法中进行UI操作相关的操作,比如加载框的显示与隐藏、动画操作等 86 | * 87 | * @param isVisible true 不可见 -> 可见 88 | * false 可见 -> 不可见 89 | */ 90 | protected void onFragmentVisibleChange(boolean isVisible) { 91 | 92 | } 93 | 94 | /** 95 | * 在Fragment首次可见时回调,可在这里进行加载数据,保证只在第一次打开Fragment时才会加载数据, 96 | * 实现懒加载,防止每次进入都重复加载数据 97 | * 该方法会在onFragmentVisibleChange()之前调用 98 | */ 99 | protected void onFragmentFirstVisible() { 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_layout_ratitngstar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 21 | 35 | 42 | 55 | 72 | 85 | 86 | 101 | 102 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 26 | 27 | 28 | 29 | 31 | 32 | 33 |
34 | 35 | 36 | 37 | xmlns:android 38 | 39 | ^$ 40 | 41 | 42 | 43 |
44 |
45 | 46 | 47 | 48 | xmlns:.* 49 | 50 | ^$ 51 | 52 | 53 | BY_NAME 54 | 55 |
56 |
57 | 58 | 59 | 60 | .*:id 61 | 62 | http://schemas.android.com/apk/res/android 63 | 64 | 65 | 66 |
67 |
68 | 69 | 70 | 71 | .*:name 72 | 73 | http://schemas.android.com/apk/res/android 74 | 75 | 76 | 77 |
78 |
79 | 80 | 81 | 82 | name 83 | 84 | ^$ 85 | 86 | 87 | 88 |
89 |
90 | 91 | 92 | 93 | style 94 | 95 | ^$ 96 | 97 | 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | ^$ 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 | 114 | 115 | 116 | .* 117 | 118 | http://schemas.android.com/apk/res/android 119 | 120 | 121 | ANDROID_ATTRIBUTE_ORDER 122 | 123 |
124 |
125 | 126 | 127 | 128 | .* 129 | 130 | .* 131 | 132 | 133 | BY_NAME 134 | 135 |
136 |
137 |
138 |
139 |
140 |
-------------------------------------------------------------------------------- /base/src/main/java/com/baymax/base/activity/BaseTitleBarActivity.java: -------------------------------------------------------------------------------- 1 | package com.baymax.base.activity; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.View; 5 | import android.widget.FrameLayout; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | import com.baymax.base.R; 10 | 11 | 12 | public abstract class BaseTitleBarActivity extends BaseActivity { 13 | 14 | private View mTitleLayout; 15 | private FrameLayout mContainer; 16 | private ImageView mBackView, mIvRight; 17 | private TextView mTitleView; 18 | 19 | @Override 20 | public void setContentView(View view) { 21 | setContentView(R.layout.activity_base_titlebar); 22 | mContainer = (FrameLayout) findViewById(R.id.base_fl_container); 23 | mContainer.addView(view); 24 | } 25 | 26 | @Override 27 | public void setContentView(int layoutResID) { 28 | super.setContentView(R.layout.activity_base_titlebar); 29 | if (layoutResID != R.layout.activity_base_titlebar) { 30 | View contentView = LayoutInflater.from(this).inflate(layoutResID, null); 31 | mContainer = findViewById(R.id.base_fl_container); 32 | mContainer.addView(contentView); 33 | } 34 | initTitleLayout(); 35 | } 36 | 37 | /** 38 | * 初始化头部Title布局 39 | */ 40 | private void initTitleLayout() { 41 | mTitleLayout = findViewById(R.id.base_layout_title); 42 | mTitleView = findViewById(R.id.base_tv_title); 43 | mBackView = findViewById(R.id.base_iv_back); 44 | mBackView.setOnClickListener(new View.OnClickListener() { 45 | @Override 46 | public void onClick(View v) { 47 | onBackViewClicked(); 48 | } 49 | }); 50 | mIvRight = findViewById(R.id.base_iv_right); 51 | mIvRight.setOnClickListener(new View.OnClickListener() { 52 | @Override 53 | public void onClick(View v) { 54 | onRightViewClicked(v); 55 | } 56 | }); 57 | } 58 | 59 | /** 60 | * 设置Title 61 | * 62 | * @param color Title布局显示的背景颜色 63 | */ 64 | public void setTitleBarBackgroundColor(int color) { 65 | if (mTitleLayout != null) { 66 | mTitleLayout.setBackgroundColor(color); 67 | } 68 | } 69 | 70 | /** 71 | * 设置Title 72 | * 73 | * @param drawable Title布局显示的背景drawable资源 74 | */ 75 | public void setTitleBarBackgroundDrawable(int drawable) { 76 | if (mTitleLayout != null) { 77 | mTitleLayout.setBackgroundResource(drawable); 78 | } 79 | } 80 | 81 | /** 82 | * 设置Title 83 | * 84 | * @param title 显示的Title 85 | */ 86 | public void setTitle(String title) { 87 | if (mTitleView != null) { 88 | mTitleView.setText(title); 89 | } 90 | } 91 | 92 | /** 93 | * 设置Title字体颜色 94 | * 95 | * @param color 96 | */ 97 | public void setTitleTextColor(int color) { 98 | if (mTitleView != null) { 99 | mTitleView.setTextColor(color); 100 | } 101 | } 102 | 103 | /** 104 | * 设置标题栏左边图片显示的图片资源 105 | * 106 | * @param resId 待显示的图片资源ID 107 | */ 108 | public void setBackViewRes(int resId) { 109 | setImageViewRes(mBackView, resId); 110 | } 111 | 112 | /** 113 | * 设置标题栏右边显示的图片资源 114 | * 115 | * @param resId 待显示的图片资源ID 116 | */ 117 | public void setRightViewRes(int resId) { 118 | setImageViewRes(mIvRight, resId); 119 | } 120 | 121 | /** 122 | * 设置Title是否显示 123 | * 124 | * @param isShow 是否显示 125 | */ 126 | public void showTitle(boolean isShow) { 127 | setViewVisibility(mTitleView, isShow); 128 | } 129 | 130 | /** 131 | * 设置返回BackView是否显示 132 | * 133 | * @param isShow 是否显示 134 | */ 135 | public void showBackView(boolean isShow) { 136 | setViewVisibility(mBackView, isShow); 137 | } 138 | 139 | /** 140 | * 设置标题栏右边图标是否显示 141 | * 142 | * @param isShow 是否显示 143 | */ 144 | public void showRightView(boolean isShow) { 145 | setViewVisibility(mIvRight, isShow); 146 | } 147 | 148 | /** 149 | * 设置View是否显示 150 | * 151 | * @param view 待处理的View 152 | * @param isShow 是否显示 153 | */ 154 | private void setViewVisibility(View view, boolean isShow) { 155 | if (view != null) { 156 | view.setVisibility(isShow ? View.VISIBLE : View.GONE); 157 | } 158 | } 159 | 160 | /** 161 | * 设置ImageView显示的资源 162 | * 163 | * @param imageView 待处理的ImageView 164 | * @param resId 待显示的图片资源ID 165 | */ 166 | private void setImageViewRes(ImageView imageView, int resId) { 167 | if (imageView != null) { 168 | imageView.setImageResource(resId); 169 | } 170 | } 171 | 172 | /** 173 | * 子页面重写该方法处理BackView点击时的处理逻辑,默认为finish当前页面 174 | */ 175 | protected void onBackViewClicked() { 176 | finish(); 177 | } 178 | 179 | /** 180 | * 子页面重写该方法处理RightView点击时的处理逻辑 181 | * 182 | * @param view 被点击的View 183 | */ 184 | protected void onRightViewClicked(View view) { 185 | 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /widget/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 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /widget/src/main/java/com/baymax/widget/RoundButton.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.graphics.RectF; 8 | import android.util.AttributeSet; 9 | 10 | import androidx.appcompat.widget.AppCompatButton; 11 | 12 | /** 13 | * @author oukanggui 14 | * @date 2019/9/26 15 | * 描述:支持圆角、描边以及背景色设置的Button 16 | */ 17 | public class RoundButton extends AppCompatButton { 18 | 19 | /** 20 | * 背景填充矩形 21 | */ 22 | RectF mFillRect; 23 | /** 24 | * 画笔 25 | */ 26 | Paint mPaint; 27 | /** 28 | * 背景填充颜色 29 | */ 30 | int mBackgroundColor; 31 | /** 32 | * 圆角x,y轴半径 33 | */ 34 | float mRadiusX = 0, mRadiusY = 0; 35 | /** 36 | * 边框颜色 37 | */ 38 | int mStrokeColor = -1; 39 | /** 40 | * 边框颜色集合 41 | */ 42 | int mStrokeColorSet = -1; 43 | /** 44 | * 边框宽度 45 | */ 46 | float mStrokeWidth = 5; 47 | /** 48 | * 字体初始化颜色 49 | */ 50 | int mTextColorInit = -1; 51 | /** 52 | * 默认圆角x,y半径 53 | */ 54 | private static final int DEFAULT_RADIUS_XY = 5; 55 | 56 | /** 57 | * @param context 58 | */ 59 | 60 | public RoundButton(Context context) { 61 | super(context); 62 | init(context, null); 63 | } 64 | 65 | public RoundButton(Context context, AttributeSet attrs) { 66 | super(context, attrs); 67 | init(context, attrs); 68 | } 69 | 70 | public RoundButton(Context context, AttributeSet attrs, int defStyleAttr) { 71 | super(context, attrs, defStyleAttr); 72 | init(context, attrs); 73 | } 74 | 75 | /** 76 | * 初始化 77 | * 78 | * @param context 79 | * @param attrs 80 | */ 81 | void init(Context context, AttributeSet attrs) { 82 | if (attrs != null) { 83 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundButton); 84 | if (a != null) { 85 | mBackgroundColor = a.getColor(R.styleable.RoundButton_backgroundColorFill, 0); 86 | mStrokeWidth = a.getDimension(R.styleable.RoundButton_strokeWidth, 1); 87 | mStrokeColor = a.getColor(R.styleable.RoundButton_strokeColor, 0); 88 | mStrokeColorSet = mStrokeColor; 89 | mRadiusX = a.getDimension(R.styleable.RoundButton_rx, DEFAULT_RADIUS_XY); 90 | mRadiusY = a.getDimension(R.styleable.RoundButton_ry, DEFAULT_RADIUS_XY); 91 | a.recycle(); 92 | } 93 | } 94 | mTextColorInit = getCurrentTextColor(); 95 | mPaint = new Paint(); 96 | mPaint.setColor(mBackgroundColor); 97 | mPaint.setStyle(Paint.Style.FILL); 98 | mPaint.setAntiAlias(true); 99 | } 100 | 101 | @Override 102 | public void setEnabled(boolean enabled) { 103 | super.setEnabled(enabled); 104 | if (enabled) { 105 | if (mStrokeColorSet != -1) { 106 | mStrokeColor = mStrokeColorSet; 107 | } 108 | if (mTextColorInit != -1) { 109 | setTextColor(mTextColorInit); 110 | } 111 | } else { 112 | int color = getContext().getResources().getColor(android.R.color.darker_gray); 113 | if (mStrokeColorSet != -1) { 114 | mStrokeColor = color; 115 | } 116 | if (mTextColorInit != -1) { 117 | setTextColor(color); 118 | } 119 | } 120 | } 121 | 122 | @Override 123 | protected void onDraw(Canvas canvas) { 124 | drawInnerFillBackground(canvas); 125 | drawOuterStroke(canvas); 126 | super.onDraw(canvas); 127 | } 128 | 129 | /** 130 | * 画内部填充背景 131 | * 132 | * @param canvas 133 | */ 134 | private void drawInnerFillBackground(Canvas canvas) { 135 | if (mBackgroundColor != 0) { 136 | if (mFillRect == null || mFillRect.width() != getMeasuredWidth()) { 137 | mFillRect = new RectF(0, 0, getMeasuredWidth() - 0, getMeasuredHeight() - 0); 138 | } 139 | mPaint.setColor(mBackgroundColor); 140 | mPaint.setStyle(Paint.Style.FILL); 141 | canvas.drawRoundRect(mFillRect, mRadiusX, mRadiusY, mPaint); 142 | } 143 | } 144 | 145 | /** 146 | * 画外部边框 147 | * 148 | * @param canvas 149 | */ 150 | private void drawOuterStroke(Canvas canvas) { 151 | if (mStrokeColor != 0) { 152 | mPaint.setColor(mStrokeColor); 153 | mPaint.setStyle(Paint.Style.STROKE); 154 | mPaint.setStrokeWidth(mStrokeWidth); 155 | RectF strokeRect = new RectF(mStrokeWidth / 2.0f, mStrokeWidth / 2.0f, getMeasuredWidth() - mStrokeWidth / 2.0f, getMeasuredHeight() - mStrokeWidth / 2.0f); 156 | canvas.drawRoundRect(strokeRect, mRadiusX, mRadiusY, mPaint); 157 | } 158 | } 159 | 160 | public void setRadiusX(float radiusX) { 161 | mRadiusX = radiusX; 162 | } 163 | 164 | public void setRadiusY(float radiusY) { 165 | mRadiusY = radiusY; 166 | } 167 | 168 | public void setmStrokeWidth(float width) { 169 | mStrokeWidth = width; 170 | } 171 | 172 | public void setFillBackgroundColor(int fillBackgroundColor) { 173 | this.mBackgroundColor = fillBackgroundColor; 174 | } 175 | 176 | 177 | public void setStrokeColor(int color) { 178 | mStrokeColor = color; 179 | mStrokeColorSet = mStrokeColor; 180 | mBackgroundColor = 0; 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_layout_arrow.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 22 | 26 | 27 | 41 | 45 | 46 | 63 | 67 | 68 | 80 | 84 | 85 | 100 | 104 | 105 | 120 | 124 | 125 | 126 | 129 | 130 | -------------------------------------------------------------------------------- /widget/src/main/java/com/baymax/widget/ArrowRelativeLayout.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widget; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.RectF; 11 | import android.util.AttributeSet; 12 | import android.widget.RelativeLayout; 13 | 14 | /** 15 | * @author Baymax 16 | * @date 2019-11-10 17 | * 描述:带三角的指示器相对布局 18 | */ 19 | public class ArrowRelativeLayout extends RelativeLayout { 20 | public static final int POSTION_TOP = 0; 21 | public static final int POSTION_BOTTOM = 1; 22 | 23 | /** 24 | * 三角形的宽度 25 | */ 26 | private float mArrowWidth = 20; 27 | /** 28 | * 三角形的高度 29 | */ 30 | private float mArrowHeight = 20; 31 | /** 32 | * 三角形左边底点距离布局左边的偏移量,默认居中显示 33 | */ 34 | private float mArrowStartOffset = -1; 35 | /** 36 | * 三角形顶点与三角形底边中心的水平偏移量,负值代表箭头往左偏,正值代表箭头往右偏,默认居中 37 | */ 38 | private float mArrowVertexOffset = 0; 39 | /** 40 | * 三角形的位置,目前支持在布局上方、在布局下方,默认在上方 41 | */ 42 | private int mArrowPosition = POSTION_TOP; 43 | /** 44 | * 圆角的X半径 45 | */ 46 | private float mRadiuX; 47 | /** 48 | * 圆角的Y半径 49 | */ 50 | private float mRadiuY; 51 | /** 52 | * 填充的颜色值 53 | */ 54 | private int mBackgroundColor = Color.RED; 55 | 56 | /** 57 | * 画笔 58 | */ 59 | private Paint mPaint; 60 | 61 | public ArrowRelativeLayout(Context context) { 62 | super(context); 63 | init(null); 64 | } 65 | 66 | public ArrowRelativeLayout(Context context, AttributeSet attrs) { 67 | super(context, attrs); 68 | init(attrs); 69 | } 70 | 71 | public ArrowRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 72 | super(context, attrs, defStyleAttr); 73 | init(attrs); 74 | } 75 | 76 | @TargetApi(21) 77 | public ArrowRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 78 | super(context, attrs, defStyleAttr, defStyleRes); 79 | init(attrs); 80 | } 81 | 82 | @Override 83 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 84 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 85 | } 86 | 87 | private void init(AttributeSet attrs) { 88 | initPaint(); 89 | initAttrs(attrs); 90 | } 91 | 92 | /** 93 | * 初始化画笔 94 | */ 95 | private void initPaint() { 96 | mPaint = new Paint(); 97 | mPaint.setAntiAlias(true); 98 | mPaint.setStyle(Paint.Style.FILL); 99 | } 100 | 101 | /** 102 | * 初始化属性 103 | * 104 | * @param attrs 105 | */ 106 | private void initAttrs(AttributeSet attrs) { 107 | if (attrs == null) { 108 | return; 109 | } 110 | TypedArray ta = getResources().obtainAttributes(attrs, R.styleable.ArrowRelativeLayout); 111 | if (ta != null) { 112 | mArrowWidth = ta.getDimension(R.styleable.ArrowRelativeLayout_arrow_width, 20); 113 | mArrowHeight = ta.getDimension(R.styleable.ArrowRelativeLayout_arrow_height, 20); 114 | mArrowStartOffset = ta.getDimension(R.styleable.ArrowRelativeLayout_arrow_start_offset, -1); 115 | mArrowVertexOffset = ta.getDimension(R.styleable.ArrowRelativeLayout_arrow_vertex_offset, 0); 116 | mArrowPosition = ta.getInt(R.styleable.ArrowRelativeLayout_arrow_position, POSTION_TOP); 117 | mRadiuX = ta.getDimension(R.styleable.ArrowRelativeLayout_radiu_x, 0); 118 | mRadiuY = ta.getDimension(R.styleable.ArrowRelativeLayout_radiu_y, 0); 119 | mBackgroundColor = ta.getColor(R.styleable.ArrowRelativeLayout_background_color, Color.GRAY); 120 | ta.recycle(); 121 | } 122 | // 根据方向重设TOP、BOTTOM的Padding值,追加三角形箭头高度mArrowHeight,以便控件内容显示在矩形区域内 123 | if (mArrowPosition == POSTION_TOP) { 124 | setPadding(getPaddingLeft(), getPaddingTop() + (int) Math.ceil(mArrowHeight), getPaddingRight(), getPaddingBottom()); 125 | } else { 126 | setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom() + (int) Math.ceil(mArrowHeight)); 127 | } 128 | } 129 | 130 | @Override 131 | protected void dispatchDraw(Canvas canvas) { 132 | mPaint.setColor(mBackgroundColor); 133 | // 画背景 134 | drawRoundRectArea(canvas, getWidth(), getHeight()); 135 | // 画箭头 136 | drawArrowArea(canvas, getWidth(), getHeight()); 137 | super.dispatchDraw(canvas); 138 | } 139 | 140 | /** 141 | * 画矩形背景 142 | * 143 | * @param canvas 144 | * @param width 145 | * @param height 146 | */ 147 | private void drawRoundRectArea(Canvas canvas, int width, int height) { 148 | //框定文本显示的区域,根据不同的方向,选择不同的矩形面积,给三角形显示的控件 149 | RectF rectF; 150 | if (mArrowPosition == POSTION_TOP) { 151 | rectF = new RectF(0, mArrowHeight, width, height); 152 | } else { 153 | rectF = new RectF(0, 0, width, height - mArrowHeight); 154 | } 155 | canvas.drawRoundRect(rectF, mRadiuX, mRadiuY, mPaint); 156 | } 157 | 158 | /** 159 | * 画三角箭头 160 | * 161 | * @param canvas 162 | * @param width 163 | * @param height 164 | */ 165 | private void drawArrowArea(Canvas canvas, int width, int height) { 166 | Path path = new Path(); 167 | // 根据三角箭头箭头方向和位置决定三角形三点的坐标 168 | if (mArrowStartOffset < 0) { 169 | mArrowStartOffset = width / 2.0f - mArrowWidth / 2.0f; 170 | } 171 | if ((mArrowStartOffset + mArrowWidth) > width) { 172 | mArrowStartOffset = width - mArrowWidth; 173 | } 174 | // 三角形左边点坐标 175 | float leftPointX = mArrowStartOffset; 176 | float leftPointY = mArrowHeight; 177 | // 顶点 178 | float topPointX = leftPointX + mArrowWidth / 2 + mArrowVertexOffset; 179 | float topPointY = 0; 180 | // 三角形右边点坐标 181 | float rightPointX = leftPointX + mArrowWidth; 182 | float rightPointY = mArrowHeight; 183 | // 根据三角形的位置,确定三角形三点的y坐标,默认在上方 184 | if (mArrowPosition == POSTION_BOTTOM) { 185 | topPointY = height; 186 | leftPointY = rightPointY = height - mArrowHeight; 187 | } 188 | // 三角形顶点 189 | path.moveTo(topPointX, topPointY); 190 | // 三角形左边的点 191 | path.lineTo(leftPointX, leftPointY); 192 | // 三角形右边的点 193 | path.lineTo(rightPointX, rightPointY); 194 | path.close(); 195 | canvas.drawPath(path, mPaint); 196 | } 197 | 198 | /** 199 | * 设置背景填充颜色 200 | * 201 | * @param backgroundColor 202 | */ 203 | public void setFillBackgroundColor(int backgroundColor) { 204 | this.mBackgroundColor = backgroundColor; 205 | invalidate(); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /widget/src/main/java/com/baymax/widget/ratingstar/StarModel.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widget.ratingstar; 2 | 3 | import android.graphics.RectF; 4 | 5 | /** 6 | * Holds all vertexes (x,y) and bounds (outerRect) info about a drawing Star. 7 | * used by {@link RatingStarView}, Most calculations (about x,y、size etc.) is done here. 8 | * 9 | *

[Based Idea or Concept]

10 | *

11 | * ## Standard Coordinate:
12 | * The coordinate is —— toward right for x+ ,toward up for y+ . 13 | *

14 | * ## 5 outer vertexes
15 | * The outer circle's (means "circumcircle") radius is 1f, original point O is the star's center, 16 | * so, the 5 vertexes at 5 outer corner is (from top A, at clockwise order): 17 | * 18 | *

  • A(0,1)
  • 19 | *
  • B(cos18°,sin18°)
  • 20 | *
  • C(cos54°,-sin54°)
  • 21 | *
  • D(-cos54°,-sin54°)
  • 22 | *
  • E(-cos18°,sin18°)
  • 23 | *

    24 | * @author 25 | * Created by hxw on 2017-04-23. 26 | * 每一个要显示的star由一个StarModel类来表示,它持有一个星星的坐标信息并完成相应的计算 27 | */ 28 | public class StarModel { 29 | 30 | public static final float DEFAULT_THICKNESS = 0.5f; 31 | public static final float MIN_THICKNESS = 0.3f; 32 | public static final float MAX_THICKNESS = 0.9f; 33 | public static final float DEFAULT_SCALE_FACTOR = 0.9511f; 34 | private float currentScaleFactor = DEFAULT_SCALE_FACTOR; 35 | private RectF outerRect = new RectF(); 36 | private float currentThicknessFactor = DEFAULT_THICKNESS; 37 | 38 | public StarModel() { 39 | this(DEFAULT_THICKNESS); 40 | } 41 | 42 | /** 43 | * @param thicknessFactor see {@link #setThickness(float)} 44 | */ 45 | public StarModel(float thicknessFactor) { 46 | reset(thicknessFactor); 47 | } 48 | 49 | /** 50 | * Reset all vertexes values to based on radius-1f, will call adjustCoordinate() automatically, 51 | * So after reset() the Coordinate is match with Android. 52 | * 53 | * @param thickness {@link #setThicknessOnStandardCoordinate } 54 | */ 55 | private void reset(float thickness) { 56 | currentScaleFactor = DEFAULT_SCALE_FACTOR; 57 | initAllVertexesToStandard(); 58 | updateOuterRect(); 59 | setThicknessOnStandardCoordinate(thickness); 60 | adjustCoordinate(); 61 | } 62 | 63 | public void setDrawingOuterRect(int left, int top, int height) { 64 | // ScaleFactor=1f means width is 1f 65 | float resizeFactor = height / aspectRatio; 66 | offsetStar(-outerRect.left, -outerRect.top); 67 | changeScaleFactor(resizeFactor); 68 | offsetStar(left, top); 69 | updateOuterRect(); 70 | } 71 | 72 | public void moveStarTo(float left, float top) { 73 | float offsetX = left - outerRect.left; 74 | // TODO 错误计算??float offsetY = left - outerRect.top; 75 | float offsetY = top - outerRect.top; 76 | offsetStar(offsetX, offsetY); 77 | updateOuterRect(); 78 | } 79 | 80 | // region vertexes fields 81 | 82 | /** 83 | * 10 float values for star's 5 vertex's (x,y) —— outer circle's radius is 1f ( 84 | * NOTE: In the "Standard Coordinate".) , first vertex is for top corner, in clockwise order. 85 | */ 86 | private static final float[] starVertexes = new float[]{ 87 | -0.9511f, 0.3090f, // E (left) 88 | 0.0000f, 1.0000f, // A (top vertex) 89 | 0.9511f, 0.3090f, // B (right) 90 | 0.5878f, -0.8090f, // C (bottom right) 91 | -0.5878f, -0.8090f, // D (bottom left) 92 | }; 93 | 94 | /** 95 | * ratio = height / width. 96 | * width is think as 1f, because the star's width is lager. 97 | * NOTE: In the "Standard Coordinate" 98 | */ 99 | private static final float aspectRatio 100 | = (starVertexes[3] - starVertexes[7]) / (starVertexes[4] - starVertexes[0]); 101 | 102 | /** 103 | * firstVertex is vertex: E (very left one) 104 | * 105 | * @see StarModel 106 | */ 107 | private VertexF firstVertex; 108 | 109 | /** 110 | * All star vertexes, from the most left one. then clockwise. 111 | *

    112 | * NOTE: init or update by {@link #initAllVertexesToStandard() } 113 | * 114 | * @see #firstVertex 115 | * @see #starVertexes 116 | */ 117 | private VertexF[] vertexes; 118 | 119 | // endregion 120 | 121 | private void initAllVertexesToStandard() { 122 | if (firstVertex == null) { 123 | firstVertex = new VertexF(starVertexes[0], starVertexes[1]); 124 | } else { 125 | firstVertex.x = starVertexes[0]; 126 | firstVertex.y = starVertexes[1]; 127 | } 128 | 129 | // create all 10 vertexes into #vertexes 130 | if (vertexes == null) { 131 | vertexes = new VertexF[10]; 132 | vertexes[0] = firstVertex; 133 | 134 | for (int i = 1; i < 10; i++) { 135 | vertexes[i] = new VertexF(); 136 | vertexes[i - 1].next = vertexes[i]; 137 | } 138 | 139 | // link tail and head 140 | vertexes[9].next = vertexes[0]; 141 | } 142 | 143 | // update all 5 outer vertexes. 144 | VertexF current = firstVertex; 145 | for (int i = 0; i < 5; i++) { 146 | current.x = starVertexes[i * 2]; 147 | current.y = starVertexes[i * 2 + 1]; 148 | 149 | current = current.next.next; 150 | } 151 | 152 | // update all 5 inner vertexes. 153 | VertexF prevOuter = firstVertex; 154 | for (int i = 0; i < 5; i++) { 155 | VertexF innerV = prevOuter.next; 156 | 157 | innerV.x = (prevOuter.x + innerV.next.x) / 2f; 158 | innerV.y = (prevOuter.y + innerV.next.y) / 2f; 159 | 160 | prevOuter = innerV.next; 161 | } 162 | } 163 | 164 | /** 165 | * Get vertex at index in {@link #vertexes} 166 | * 167 | * @param index see {@link #vertexes} 168 | */ 169 | public VertexF getVertex(int index) { 170 | return vertexes[index]; 171 | } 172 | 173 | public RectF getOuterRect() { 174 | return new RectF(outerRect); 175 | } 176 | 177 | /** 178 | * Keep the star's outer bounds exactly. 179 | * NOTE: call this after any vertex value changed. 180 | */ 181 | private void updateOuterRect() { 182 | outerRect.top = vertexes[2].y; 183 | outerRect.right = vertexes[4].x; 184 | outerRect.bottom = vertexes[8].y; 185 | outerRect.left = vertexes[0].x; 186 | } 187 | 188 | private void offsetStar(float left, float top) { 189 | for (int i = 0; i < vertexes.length; i++) { 190 | vertexes[i].x += left; 191 | vertexes[i].y += top; 192 | } 193 | } 194 | 195 | private void changeScaleFactor(float newFactor) { 196 | float scale = newFactor / currentScaleFactor; 197 | if (scale == 1f) { 198 | return; 199 | } 200 | for (int i = 0; i < vertexes.length; i++) { 201 | vertexes[i].x *= scale; 202 | vertexes[i].y *= scale; 203 | } 204 | currentScaleFactor = newFactor; 205 | } 206 | 207 | /** 208 | * change the thickness of star. 209 | * value {@link #DEFAULT_THICKNESS}is about to make a standard star. 210 | * 211 | * @param factor between {@link #MIN_THICKNESS} and {@link #MAX_THICKNESS}. 212 | */ 213 | public void setThickness(float factor) { 214 | if (currentThicknessFactor == factor) { 215 | return; 216 | } 217 | float oldScale = currentScaleFactor; 218 | float left = outerRect.left; 219 | float top = outerRect.top; 220 | 221 | reset(factor); 222 | 223 | changeScaleFactor(oldScale); 224 | moveStarTo(left, top); 225 | } 226 | 227 | private void setThicknessOnStandardCoordinate(float thicknessFactor) { 228 | if (thicknessFactor < MIN_THICKNESS) { 229 | thicknessFactor = MIN_THICKNESS; 230 | } else if (thicknessFactor > MAX_THICKNESS) { 231 | thicknessFactor = MAX_THICKNESS; 232 | } 233 | 234 | for (int i = 1; i < vertexes.length; i += 2) { 235 | vertexes[i].x *= thicknessFactor; 236 | vertexes[i].y *= thicknessFactor; 237 | } 238 | 239 | currentThicknessFactor = thicknessFactor; 240 | } 241 | 242 | /** 243 | * reverse Y, and move to y=0 244 | */ 245 | private void adjustCoordinate() { 246 | float offsetX = -outerRect.left; 247 | float offsetY = outerRect.top; 248 | 249 | for (int i = 0; i < vertexes.length; i++) { 250 | vertexes[i].y = -vertexes[i].y + offsetY; 251 | vertexes[i].x += offsetX; 252 | 253 | // standard value is in radius = 1f, so.. 254 | vertexes[i].x /= 2f; 255 | vertexes[i].y /= 2f; 256 | } 257 | 258 | updateOuterRect(); 259 | } 260 | 261 | /** 262 | * ratio = height / width. width is think as 1f, because the star's width is lager. 263 | * NOTE: In the "Standard Coordinate" 264 | * 265 | * @return ratio = height / width. 266 | */ 267 | public static float getOuterRectAspectRatio() { 268 | return aspectRatio; 269 | } 270 | 271 | public static float getStarWidth(float starHeight) { 272 | return starHeight / getOuterRectAspectRatio(); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /widget/src/main/java/com/baymax/widget/MenuLayout.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widget; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.drawable.Drawable; 7 | import android.os.Build; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.view.View; 11 | import android.widget.ImageView; 12 | import android.widget.RelativeLayout; 13 | import android.widget.TextView; 14 | 15 | import androidx.annotation.Dimension; 16 | 17 | /** 18 | * @author oukanggui 19 | * @date 2019/9/26 20 | * 描述:设置菜单条Layout 21 | */ 22 | public class MenuLayout extends RelativeLayout { 23 | 24 | /** 25 | * Item根布局 26 | */ 27 | private View mItemLayout; 28 | /** 29 | * 左侧图标 30 | */ 31 | private ImageView mIconLeft; 32 | /** 33 | * 右侧图标 34 | */ 35 | private ImageView mIconRight; 36 | /** 37 | * 左侧文字 38 | */ 39 | private TextView mTextLeft; 40 | /** 41 | * 右侧文字 42 | */ 43 | private TextView mTextRight; 44 | /** 45 | * 底部分隔线 46 | */ 47 | private View mLineView; 48 | 49 | public MenuLayout(Context context) { 50 | super(context); 51 | init(context, null); 52 | } 53 | 54 | public MenuLayout(Context context, AttributeSet attrs) { 55 | super(context, attrs); 56 | init(context, attrs); 57 | } 58 | 59 | public MenuLayout(Context context, AttributeSet attrs, int defStyleAttr) { 60 | super(context, attrs, defStyleAttr); 61 | init(context, attrs); 62 | } 63 | 64 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 65 | public MenuLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 66 | super(context, attrs, defStyleAttr, defStyleRes); 67 | init(context, attrs); 68 | } 69 | 70 | /** 71 | * 初始化,进行控件和属性初始化工作 72 | * 73 | * @param context 74 | * @param attrs 75 | */ 76 | private void init(Context context, AttributeSet attrs) { 77 | // MenuLayout作为R.layout.layout_menu_item的父View,最后内部通过addView的形式添加到MenuLayout中 78 | inflate(context, R.layout.layout_menu_item, this); 79 | mIconLeft = findViewById(R.id.menu_iv_left); 80 | mIconRight = findViewById(R.id.menu_iv_right); 81 | mTextLeft = findViewById(R.id.menu_tv_left); 82 | mTextRight = findViewById(R.id.menu_tv_right); 83 | mLineView = findViewById(R.id.menu_line); 84 | mItemLayout = findViewById(R.id.menu_item_layout); 85 | if (attrs != null) { 86 | TypedArray typedArray = getResources().obtainAttributes(attrs, R.styleable.MenuLayout); 87 | if (typedArray != null) { 88 | int backgroundColorId = typedArray.getColor(R.styleable.MenuLayout_itemBackgroundColor, 0); 89 | setMenuBackgroundColor(backgroundColorId); 90 | 91 | int leftIconResId = typedArray.getResourceId(R.styleable.MenuLayout_leftIcon, 0); 92 | setLeftIcon(leftIconResId); 93 | int rightIconResId = typedArray.getResourceId(R.styleable.MenuLayout_rightIcon, 0); 94 | setRightIcon(rightIconResId); 95 | 96 | String textLeft = typedArray.getString(R.styleable.MenuLayout_leftText); 97 | setLeftText(textLeft); 98 | String textRight = typedArray.getString(R.styleable.MenuLayout_rightText); 99 | setRightText(textRight); 100 | 101 | int leftTextColorId = typedArray.getColor(R.styleable.MenuLayout_leftTextColor, 0); 102 | setLeftTextColor(leftTextColorId); 103 | int rightTextColorId = typedArray.getColor(R.styleable.MenuLayout_rightTextColor, 0); 104 | setRightTextColor(rightTextColorId); 105 | 106 | int spLeftTextSize = typedArray.getInteger(R.styleable.MenuLayout_leftTextSize, 0); 107 | setLeftTextSize(spLeftTextSize); 108 | int spRightTextSize = typedArray.getInteger(R.styleable.MenuLayout_rightTextSize, 0); 109 | setRightTextSize(spRightTextSize); 110 | 111 | boolean isBottomLineVisible = typedArray.getBoolean(R.styleable.MenuLayout_lineVisible, true); 112 | if (mLineView != null) { 113 | mLineView.setVisibility(isBottomLineVisible ? View.VISIBLE : View.GONE); 114 | } 115 | 116 | typedArray.recycle(); 117 | } 118 | } 119 | } 120 | 121 | /** 122 | * 获取Menu Item根布局 123 | */ 124 | public View getMenuView() { 125 | return mItemLayout; 126 | } 127 | 128 | /** 129 | * 获取左边图标对象 130 | * 131 | * @return 132 | */ 133 | public ImageView getIconLeft() { 134 | return mIconLeft; 135 | } 136 | 137 | /** 138 | * 获取右边图标对象 139 | * 140 | * @return 141 | */ 142 | public ImageView getIconRight() { 143 | return mIconRight; 144 | } 145 | 146 | /** 147 | * 通过资源id设置左边图片资源 148 | * 149 | * @param resId 150 | */ 151 | public void setLeftIcon(int resId) { 152 | if (mIconLeft != null) { 153 | if (resId != 0) { 154 | setViewVisible(mIconLeft); 155 | mIconLeft.setImageResource(resId); 156 | } else { 157 | // 资源id == 0,默认为不配置 158 | setViewInvisible(mIconLeft); 159 | } 160 | } 161 | } 162 | 163 | /** 164 | * 通过Drawable设置左边图片资源 165 | * 166 | * @param drawable 167 | */ 168 | public void setLeftIcon(Drawable drawable) { 169 | if (mIconLeft != null) { 170 | setViewVisible(mIconLeft); 171 | mIconLeft.setImageDrawable(drawable); 172 | } 173 | } 174 | 175 | /** 176 | * 通过资源id设置右边图片资源 177 | * 178 | * @param resId 179 | */ 180 | public void setRightIcon(int resId) { 181 | if (mIconRight != null) { 182 | if (resId != 0) { 183 | setViewVisible(mIconRight); 184 | mIconRight.setImageResource(resId); 185 | } else { 186 | // 资源id == 0,默认为不配置 187 | setViewInvisible(mIconRight); 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * 通过Drawable设置左边图片资源 194 | * 195 | * @param drawable 196 | */ 197 | public void setRightIcon(Drawable drawable) { 198 | if (mIconRight != null) { 199 | setViewVisible(mIconRight); 200 | mIconRight.setImageDrawable(drawable); 201 | } 202 | } 203 | 204 | /** 205 | * 设置左边显示文字 206 | * 207 | * @param resId 208 | */ 209 | public void setLeftText(int resId) { 210 | if (mTextLeft != null) { 211 | setViewVisible(mTextLeft); 212 | mTextLeft.setText(resId); 213 | } 214 | } 215 | 216 | /** 217 | * 设置左边显示文字 218 | * 219 | * @param text 220 | */ 221 | public void setLeftText(String text) { 222 | if (mTextLeft != null) { 223 | setViewVisible(mTextLeft); 224 | mTextLeft.setText(text); 225 | } 226 | } 227 | 228 | /** 229 | * 设置右边显示文字 230 | * 231 | * @param resId 232 | */ 233 | public void setRightText(int resId) { 234 | if (mTextRight != null) { 235 | setViewVisible(mTextRight); 236 | mTextRight.setText(resId); 237 | } 238 | } 239 | 240 | /** 241 | * 设置右边显示文字 242 | * 243 | * @param text 244 | */ 245 | public void setRightText(String text) { 246 | if (mTextRight != null) { 247 | setViewVisible(mTextRight); 248 | mTextRight.setText(text); 249 | } 250 | } 251 | 252 | /** 253 | * 设置左边文字颜色 254 | * 255 | * @param colorId 256 | */ 257 | public void setLeftTextColor(int colorId) { 258 | // colorId = 0,使用默认颜色 259 | if (mTextLeft != null && colorId != 0) { 260 | mTextLeft.setTextColor(colorId); 261 | } 262 | } 263 | 264 | /** 265 | * 设置右边文字颜色 266 | * 267 | * @param colorId 268 | */ 269 | public void setRightTextColor(int colorId) { 270 | // colorId = 0,使用默认颜色 271 | if (mTextRight != null && colorId != 0) { 272 | mTextRight.setTextColor(colorId); 273 | } 274 | } 275 | 276 | /** 277 | * 设置左边字体大小 278 | * 279 | * @param spSize sp 280 | */ 281 | public void setLeftTextSize(int spSize) { 282 | // pxSize == 0时,使用默认布局的字体大小 283 | if (mTextLeft != null && spSize > 0) { 284 | mTextLeft.setTextSize(Dimension.SP, spSize); 285 | } 286 | } 287 | 288 | /** 289 | * 设置右边字体大小 290 | * 291 | * @param spSize sp 292 | */ 293 | public void setRightTextSize(int spSize) { 294 | // pxSize == 0时,使用默认布局的字体大小 295 | if (mTextRight != null && spSize > 0) { 296 | mTextRight.setTextSize(Dimension.SP, spSize); 297 | } 298 | } 299 | 300 | /** 301 | * 设置背景颜色 302 | * 303 | * @param colorId 304 | */ 305 | public void setMenuBackgroundColor(int colorId) { 306 | if (mItemLayout != null && colorId != 0) { 307 | mItemLayout.setBackgroundColor(colorId); 308 | } 309 | } 310 | 311 | /** 312 | * 设置背景 313 | * 314 | * @param resId 315 | */ 316 | public void setMenuBackground(int resId) { 317 | if (mItemLayout != null) { 318 | mItemLayout.setBackgroundResource(resId); 319 | } 320 | } 321 | 322 | /** 323 | * 设置View可见 324 | * 325 | * @param view 326 | */ 327 | private void setViewVisible(View view) { 328 | if (view != null) { 329 | view.setVisibility(View.VISIBLE); 330 | } 331 | } 332 | 333 | /** 334 | * 设置View不可见(保留原位置信息) 335 | * 336 | * @param view 337 | */ 338 | private void setViewInvisible(View view) { 339 | if (view != null) { 340 | view.setVisibility(View.INVISIBLE); 341 | } 342 | } 343 | 344 | 345 | } 346 | -------------------------------------------------------------------------------- /widget/src/main/java/com/baymax/widget/ratingstar/RatingStarView.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widget.ratingstar; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.CornerPathEffect; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.RectF; 11 | import android.os.Build; 12 | import android.util.AttributeSet; 13 | import android.util.Log; 14 | import android.view.MotionEvent; 15 | import android.view.View; 16 | 17 | import com.baymax.widget.R; 18 | 19 | import java.util.ArrayList; 20 | 21 | /** 22 | * @author oukanggui 23 | * @date 2019/11/18 24 | * 描述:强大的星星评分控件View,参考优化改造于GitHub https://github.com/everhad/AndroidRatingStar 25 | * RatingStar is specific RatingBar use star drawable as the progress mark. 26 | *

    27 | * NOTE: 28 | * Padding will be larger if is {@link #cornerRadius} is set (No exact calc to handle this issue). 29 | */ 30 | public class RatingStarView extends View implements View.OnClickListener { 31 | private static final String TAG = "RatingStarView"; 32 | private static final int DEFAULT_STAR_HEIGHT = 32; 33 | private float cornerRadius = 4f; 34 | private int starForegroundColor = 0xffED4A4B; 35 | private int strokeColor = 0xffED4A4B; 36 | private int starBackgroundColor = Color.WHITE; 37 | /** 38 | * used to make round smooth star horn 39 | */ 40 | private CornerPathEffect pathEffect; 41 | private ArrayList starList; 42 | private float rating; 43 | /** 44 | * expected star number. 45 | */ 46 | private int starNum = 5; 47 | /** 48 | * real drawn star number. 49 | */ 50 | private int starCount; 51 | /** 52 | * calculated value 53 | */ 54 | private float starWidth; 55 | /** 56 | * calculated value 57 | */ 58 | private float starHeight; 59 | private float starMargin = 8; 60 | private float strokeWidth = 0; 61 | private boolean drawStrokeForFullStar = false; 62 | private boolean drawStrokeForHalfStar = false; 63 | private boolean drawStrokeForEmptyStar = false; 64 | private boolean enableSelectRating = false; 65 | private boolean onlyHalfStar = false; 66 | private float starThicknessFactor = StarModel.DEFAULT_THICKNESS; 67 | private float dividerX; 68 | private float clickedX, clickedY; 69 | private Paint paint; 70 | private OnClickListener mOuterOnClickListener; 71 | 72 | public RatingStarView(Context context) { 73 | super(context); 74 | init(null, 0); 75 | } 76 | 77 | public RatingStarView(Context context, AttributeSet attrs) { 78 | super(context, attrs); 79 | init(attrs, 0); 80 | } 81 | 82 | public RatingStarView(Context context, AttributeSet attrs, int defStyle) { 83 | super(context, attrs, defStyle); 84 | init(attrs, defStyle); 85 | } 86 | // endregion 87 | 88 | private void init(AttributeSet attrs, int defStyle) { 89 | loadAttributes(attrs, defStyle); 90 | 91 | // init paint 92 | paint = new Paint(); 93 | paint.setFlags(Paint.ANTI_ALIAS_FLAG); 94 | paint.setStrokeWidth(strokeWidth); 95 | 96 | // properties 97 | pathEffect = new CornerPathEffect(cornerRadius); 98 | 99 | // click to rate 100 | super.setOnClickListener(this); 101 | } 102 | 103 | private void loadAttributes(AttributeSet attrs, int defStyle) { 104 | final TypedArray a = getContext().obtainStyledAttributes( 105 | attrs, R.styleable.RatingStarView, defStyle, 0); 106 | strokeColor = a.getColor(R.styleable.RatingStarView_rsv_strokeColor, strokeColor); 107 | starForegroundColor = a.getColor(R.styleable.RatingStarView_rsv_starForegroundColor, starForegroundColor); 108 | starBackgroundColor = a.getColor(R.styleable.RatingStarView_rsv_starBackgroundColor, starBackgroundColor); 109 | cornerRadius = a.getDimension(R.styleable.RatingStarView_rsv_cornerRadius, cornerRadius); 110 | starMargin = a.getDimension(R.styleable.RatingStarView_rsv_starMargin, starMargin); 111 | strokeWidth = a.getDimension(R.styleable.RatingStarView_rsv_strokeWidth, strokeWidth); 112 | starThicknessFactor = a.getFloat(R.styleable.RatingStarView_rsv_starThickness, starThicknessFactor); 113 | rating = a.getFloat(R.styleable.RatingStarView_rsv_rating, rating); 114 | starNum = a.getInteger(R.styleable.RatingStarView_rsv_starNum, starNum); 115 | drawStrokeForEmptyStar = a.getBoolean(R.styleable.RatingStarView_rsv_drawStrokeForEmptyStar, drawStrokeForEmptyStar); 116 | drawStrokeForFullStar = a.getBoolean(R.styleable.RatingStarView_rsv_drawStrokeForFullStar, drawStrokeForFullStar); 117 | drawStrokeForFullStar = a.getBoolean(R.styleable.RatingStarView_rsv_drawStrokeForHalfStar, drawStrokeForFullStar); 118 | enableSelectRating = a.getBoolean(R.styleable.RatingStarView_rsv_enableSelectRating, enableSelectRating); 119 | onlyHalfStar = a.getBoolean(R.styleable.RatingStarView_rsv_onlyHalfStar, onlyHalfStar); 120 | a.recycle(); 121 | } 122 | 123 | private void setStarBackgroundColor(int color) { 124 | starBackgroundColor = color; 125 | invalidate(); 126 | } 127 | 128 | /** 129 | * @see StarModel#setThickness(float) 130 | */ 131 | public void setStarThickness(float thicknessFactor) { 132 | for (StarModel star : starList) { 133 | star.setThickness(thicknessFactor); 134 | } 135 | invalidate(); 136 | } 137 | 138 | public void setStrokeWidth(float width) { 139 | this.strokeWidth = width; 140 | invalidate(); 141 | } 142 | 143 | /** 144 | * Finally progress is: progress = rating / starNum 145 | * 146 | * @param rating should be [0, starNum] 147 | */ 148 | public void setRating(float rating) { 149 | if (rating != this.rating) { 150 | this.rating = rating; 151 | invalidate(); 152 | } 153 | } 154 | 155 | /** 156 | * Set the smooth of the star's horn. 157 | * 158 | * @param cornerRadius corner circle radius 159 | */ 160 | public void setCornerRadius(float cornerRadius) { 161 | this.cornerRadius = cornerRadius; 162 | invalidate(); 163 | } 164 | 165 | /** 166 | * The horizontal margin between two stars. The {@link #setCornerRadius} would make extra space 167 | * as it make the star smaller. 168 | * 169 | * @param margin horizontal space 170 | */ 171 | public void setStarMargin(int margin) { 172 | this.starMargin = margin; 173 | calcStars(); 174 | invalidate(); 175 | } 176 | 177 | /** 178 | * How many stars to show, one star means one score = 1f. See {@link #setRating(float)}
    179 | * NOTE: The star's height is made by contentHeight by default.So, be sure to has defined the 180 | * correct StarView's height. 181 | * 182 | * @param count star count. 183 | */ 184 | public void setStarNum(int count) { 185 | if (starNum != count) { 186 | starNum = count; 187 | calcStars(); 188 | invalidate(); 189 | } 190 | } 191 | 192 | private void onPaddingChanged() { 193 | int left = getPaddingLeft(); 194 | int top = getPaddingTop(); 195 | for (StarModel star : starList) { 196 | star.moveStarTo(left, top); 197 | } 198 | } 199 | 200 | public void setDrawStrokeForFullStar(boolean draw) { 201 | drawStrokeForFullStar = draw; 202 | } 203 | 204 | public void setDrawStrokeForEmptyStar(boolean draw) { 205 | drawStrokeForEmptyStar = draw; 206 | } 207 | 208 | /** 209 | * Create all stars data, according to the contentWidth/contentHeight. 210 | */ 211 | private void calcStars() { 212 | int paddingLeft = getPaddingLeft(); 213 | int paddingTop = getPaddingTop(); 214 | int paddingRight = getPaddingRight(); 215 | int paddingBottom = getPaddingBottom(); 216 | int contentWidth = getWidth() - paddingLeft - paddingRight; 217 | int contentHeight = getHeight() - paddingTop - paddingBottom; 218 | 219 | int left = paddingLeft; 220 | int top = paddingTop; 221 | 222 | // according to the View's height , make star height. 223 | int starHeight = contentHeight; 224 | if (contentHeight > contentWidth) { 225 | starHeight = contentWidth; 226 | } 227 | 228 | if (starHeight <= 0) { 229 | return; 230 | } 231 | float startWidth = StarModel.getStarWidth(starHeight); 232 | 233 | // starCount * startWidth + (starCount - 1) * starMargin = contentWidth 234 | int starCount = (int) ((contentWidth + starMargin) / (startWidth + starMargin)); 235 | if (starCount > starNum) { 236 | starCount = starNum; 237 | } 238 | 239 | this.starHeight = starHeight; 240 | this.starWidth = startWidth; 241 | Log.d(TAG, "drawing starCount = " + starCount + ", contentWidth = " + contentWidth 242 | + ", startWidth = " + startWidth + ", starHeight = " + starHeight); 243 | 244 | starList = new ArrayList<>(starCount); 245 | 246 | for (int i = 0; i < starCount; i++) { 247 | StarModel star = new StarModel(starThicknessFactor); 248 | starList.add(star); 249 | star.setDrawingOuterRect(left, top, starHeight); 250 | left += startWidth + 0.5f + starMargin; 251 | } 252 | 253 | this.starCount = starCount; 254 | this.starWidth = startWidth; 255 | this.starHeight = starHeight; 256 | } 257 | 258 | @Override 259 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 260 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 261 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 262 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 263 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 264 | 265 | float width; 266 | int height; // must have height 267 | 268 | if (heightMode == MeasureSpec.EXACTLY) { 269 | // Parent has told us how big to be. So be it. 270 | height = heightSize; 271 | } else { 272 | height = DEFAULT_STAR_HEIGHT; 273 | if (heightMode == MeasureSpec.AT_MOST) { 274 | height = Math.min(height, heightSize); 275 | } 276 | } 277 | 278 | float starHeight = height - getPaddingBottom() - getPaddingTop(); 279 | 280 | if (widthMode == MeasureSpec.EXACTLY) { 281 | // Parent has told us how big to be. So be it. 282 | width = widthSize; 283 | } else { 284 | // get the perfect width 285 | width = getPaddingLeft() + getPaddingRight(); 286 | if (starNum > 0) { 287 | if (starHeight > 0) { 288 | width += starMargin * (starNum - 1); 289 | width += StarModel.getStarWidth(starHeight) * starNum; 290 | } 291 | } 292 | 293 | if (widthMode == MeasureSpec.AT_MOST) { 294 | width = Math.min(widthSize, width); 295 | } 296 | } 297 | 298 | Log.d(TAG, "[onMeasure] width = " + width + ", pLeft = " + getPaddingLeft() 299 | + ", pRight = " + getPaddingRight() + ", starMargin = " + starMargin 300 | + ", starHeight = " + starHeight + ", starWidth = " + StarModel.getStarWidth(starHeight)); 301 | 302 | int widthInt = (int) (width); 303 | if (widthInt < width) { 304 | widthInt++; 305 | } 306 | 307 | setMeasuredDimension(widthInt, height); 308 | } 309 | 310 | @Override 311 | protected void onDraw(Canvas canvas) { 312 | super.onDraw(canvas); 313 | 314 | if (starList == null) { 315 | calcStars(); 316 | } 317 | 318 | if (starList == null || starList.size() == 0) { 319 | return; 320 | } 321 | 322 | for (int i = 0; i < starList.size(); i++) { 323 | if (rating >= i + 1) { 324 | // 满星星 325 | drawFullStar(starList.get(i), canvas); 326 | } else { 327 | float decimal = rating - i; 328 | if (decimal > 0) { 329 | if (onlyHalfStar) { 330 | decimal = 0.5f; 331 | } 332 | //小数星星 333 | drawPartialStar(starList.get(i), canvas, decimal); 334 | } else { 335 | //空星星 336 | drawEmptyStar(starList.get(i), canvas); 337 | } 338 | } 339 | } 340 | } 341 | 342 | private void drawFullStar(StarModel star, Canvas canvas) { 343 | drawSolidStar(star, canvas, starForegroundColor); 344 | if (drawStrokeForFullStar) { 345 | drawStarStroke(star, canvas); 346 | } 347 | } 348 | 349 | private void drawEmptyStar(StarModel star, Canvas canvas) { 350 | drawSolidStar(star, canvas, starBackgroundColor); 351 | if (drawStrokeForEmptyStar) { 352 | drawStarStroke(star, canvas); 353 | } 354 | } 355 | 356 | @Override 357 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 358 | super.onSizeChanged(w, h, oldw, oldh); 359 | if (h != oldh) { 360 | calcStars(); 361 | } 362 | } 363 | 364 | private void drawPartialStar(StarModel star, Canvas canvas, float percent) { 365 | Log.d(TAG, "drawPartialStar percent = " + percent); 366 | if (percent <= 0) { 367 | drawEmptyStar(star, canvas); 368 | return; 369 | } else if (percent >= 1) { 370 | drawFullStar(star, canvas); 371 | return; 372 | } 373 | 374 | // layer 1--空星星 375 | drawSolidStar(star, canvas, starBackgroundColor); 376 | 377 | float dividerX = star.getOuterRect().left + star.getOuterRect().width() * percent; 378 | this.dividerX = dividerX; 379 | 380 | // layer 2 单独定义一个与星星等大的图层画布图层,后面的所用操作均在改图层进行绘制,直到调用canvas.restore() 381 | RectF r = star.getOuterRect(); 382 | //canvas.saveLayerAlpha(r.left, r.top, r.right, r.bottom, 0xff, CLIP_SAVE_FLAG); 383 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 384 | canvas.saveLayerAlpha(r.left, r.top, r.right, r.bottom, 0xff); 385 | } else { 386 | canvas.saveLayerAlpha(r.left, r.top, r.right, r.bottom, 0xff, Canvas.ALL_SAVE_FLAG); 387 | } 388 | RectF clip = new RectF(star.getOuterRect()); 389 | clip.right = dividerX; 390 | canvas.clipRect(clip); 391 | drawSolidStar(star, canvas, starForegroundColor); 392 | canvas.restore(); 393 | 394 | // layer 1 395 | if (drawStrokeForHalfStar) { 396 | drawStarStroke(star, canvas); 397 | } 398 | } 399 | 400 | private void drawSolidStar(StarModel star, Canvas canvas, int fillColor) { 401 | paint.setStyle(Paint.Style.FILL_AND_STROKE); 402 | paint.setColor(fillColor); 403 | paint.setPathEffect(pathEffect); 404 | 405 | VertexF prev = star.getVertex(1); 406 | Path path = new Path(); 407 | path.moveTo(prev.x, prev.y); 408 | 409 | for (int i = 0; i < 5; i++) { 410 | VertexF next = prev.next; 411 | path.lineTo(next.x, next.y); 412 | path.lineTo(next.next.x, next.next.y); 413 | path.lineTo(next.next.x, next.next.y); 414 | prev = next.next; 415 | } 416 | canvas.drawPath(path, paint); 417 | } 418 | 419 | private void drawStarStroke(StarModel star, Canvas canvas) { 420 | paint.setStyle(Paint.Style.STROKE); 421 | paint.setColor(strokeColor); 422 | paint.setPathEffect(pathEffect); 423 | VertexF prev = star.getVertex(1); 424 | Path path = new Path(); 425 | 426 | for (int i = 0; i < 5; i++) { 427 | path.rewind(); 428 | path.moveTo(prev.x, prev.y); 429 | 430 | VertexF next = prev.next; 431 | 432 | path.lineTo(next.x, next.y); 433 | path.lineTo(next.next.x, next.next.y); 434 | path.lineTo(next.next.x, next.next.y); 435 | 436 | canvas.drawPath(path, paint); 437 | prev = next.next; 438 | } 439 | } 440 | 441 | @Override 442 | public boolean onTouchEvent(MotionEvent event) { 443 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 444 | clickedX = event.getX(); 445 | clickedY = event.getY(); 446 | } 447 | return super.onTouchEvent(event); 448 | } 449 | 450 | @Override 451 | public void setOnClickListener(OnClickListener l) { 452 | mOuterOnClickListener = l; 453 | } 454 | 455 | @Override 456 | public void onClick(View v) { 457 | if (mOuterOnClickListener != null) { 458 | mOuterOnClickListener.onClick(v); 459 | } 460 | 461 | if (enableSelectRating) { 462 | changeRatingByClick(); 463 | } 464 | } 465 | 466 | private void changeRatingByClick() { 467 | int paddingTop = getPaddingTop(); 468 | if (clickedY < paddingTop || clickedY > paddingTop + starHeight) { 469 | return; 470 | } 471 | 472 | int paddingLeft = getPaddingLeft(); 473 | float starWidth = this.starWidth; 474 | float starMargin = this.starMargin; 475 | 476 | float left = paddingLeft; 477 | for (int i = 1; i <= starCount; i++) { 478 | float right = left + starWidth; 479 | if (clickedX >= left && clickedX <= right) { 480 | if (this.rating == i) { 481 | setRating(0); 482 | } else { 483 | setRating(i); 484 | } 485 | break; 486 | } 487 | 488 | left += (starWidth + starMargin); 489 | } 490 | } 491 | 492 | public float getRating() { 493 | return rating; 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /widget/src/main/java/com/baymax/widget/ExpandLayout.java: -------------------------------------------------------------------------------- 1 | package com.baymax.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.os.Build; 6 | import android.text.Layout; 7 | import android.text.StaticLayout; 8 | import android.text.TextPaint; 9 | import android.text.TextUtils; 10 | import android.util.AttributeSet; 11 | import android.util.Log; 12 | import android.util.TypedValue; 13 | import android.view.View; 14 | import android.view.ViewTreeObserver; 15 | import android.widget.ImageView; 16 | import android.widget.LinearLayout; 17 | import android.widget.RelativeLayout; 18 | import android.widget.TextView; 19 | 20 | /** 21 | * @author oukanggui 22 | * @date 2019/10/18 23 | * 描述:展开和收起布局,在TextView最后一行添加“图片+文字样式”展开和收起布局,而且是右对齐 24 | * 优化改造于:https://blog.csdn.net/u014620028/article/details/100145527 25 | */ 26 | public class ExpandLayout extends RelativeLayout implements View.OnClickListener { 27 | private static final String TAG = ExpandLayout.class.getSimpleName(); 28 | private static final int STYLE_DEFAULT = 0; 29 | private static final int STYLE_ICON = 1; 30 | private static final int STYLE_TEXT = 2; 31 | private Context mContext; 32 | private View mRootView; 33 | private TextView mTvContent; 34 | private LinearLayout mLayoutExpandMore; 35 | private ImageView mIconExpand; 36 | private TextView mTvExpand; 37 | private int mMeasuredWidth; 38 | /** 39 | * 辅助TextView,保证末尾图标和文字与内容文字居中显示 40 | */ 41 | private TextView mTvExpandHelper; 42 | 43 | private int mExpandIconResId; 44 | private int mCollapseIconResId; 45 | 46 | private String mExpandMoreStr; 47 | private String mCollapseLessStr; 48 | 49 | private int mMaxLines = 2; 50 | 51 | private int mContentTextSize; 52 | private int mExpandTextSize; 53 | 54 | /** 55 | * 是否展开 56 | */ 57 | private boolean mIsExpand = false; 58 | 59 | /** 60 | * 原内容文本 61 | */ 62 | private String mOriginContentStr; 63 | /** 64 | * 缩略后展示的文本 65 | */ 66 | private CharSequence mEllipsizeStr; 67 | 68 | /** 69 | * 主文字颜色 70 | */ 71 | private int mContentTextColor; 72 | /** 73 | * 展开/收起文字颜色 74 | */ 75 | private int mExpandTextColor; 76 | 77 | /** 78 | * 样式,默认为图标+文字样式 79 | */ 80 | private int mExpandStyle = STYLE_DEFAULT; 81 | 82 | /** 83 | * 展开/收缩布局对应图标的宽度,默认布局是15dp 84 | */ 85 | private int mExpandIconWidth = 15; 86 | 87 | /** 88 | * 缩略文本展示时与展开/搜索布局的间距,默认是20dp 89 | */ 90 | private int mSpaceMargin = 20; 91 | 92 | /** 93 | * 文本显示的lineSpacingExtra,对应于TextView的lineSpacingExtra属性 94 | */ 95 | private float mLineSpacingExtra = 0.0f; 96 | 97 | /** 98 | * 文本显示的lineSpacingMultiplier,对应于TextView的lineSpacingMultiplier属性 99 | */ 100 | private float mLineSpacingMultiplier = 1.0f; 101 | 102 | 103 | private OnExpandStateChangeListener mOnExpandStateChangeListener; 104 | 105 | /** 106 | * 监听器 107 | */ 108 | public interface OnExpandStateChangeListener { 109 | /** 110 | * 展开时回调 111 | */ 112 | void onExpand(); 113 | 114 | /** 115 | * 收起时回调 116 | */ 117 | void onCollapse(); 118 | 119 | } 120 | 121 | public ExpandLayout(Context context) { 122 | this(context, null); 123 | } 124 | 125 | public ExpandLayout(Context context, AttributeSet attrs) { 126 | this(context, attrs, 0); 127 | } 128 | 129 | public ExpandLayout(Context context, AttributeSet attrs, int defStyleAttr) { 130 | super(context, attrs, defStyleAttr); 131 | mContext = context; 132 | initAttributeSet(context, attrs); 133 | initView(); 134 | } 135 | 136 | /** 137 | * 初始化自定义属性 138 | * @param context 139 | * @param attrs 140 | */ 141 | private void initAttributeSet(Context context, AttributeSet attrs){ 142 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ExpandLayout); 143 | if (ta != null) { 144 | mMaxLines = ta.getInt(R.styleable.ExpandLayout_maxLines, 2); 145 | mExpandIconResId = ta.getResourceId(R.styleable.ExpandLayout_expandIconResId, 0); 146 | mCollapseIconResId = ta.getResourceId(R.styleable.ExpandLayout_collapseIconResId, 0); 147 | mExpandMoreStr = ta.getString(R.styleable.ExpandLayout_expandMoreText); 148 | mCollapseLessStr = ta.getString(R.styleable.ExpandLayout_collapseLessText); 149 | mContentTextSize = ta.getDimensionPixelSize(R.styleable.ExpandLayout_contentTextSize, sp2px(context, 14)); 150 | mContentTextColor = ta.getColor(R.styleable.ExpandLayout_contentTextColor, 0); 151 | mExpandTextSize = ta.getDimensionPixelSize(R.styleable.ExpandLayout_expandTextSize, sp2px(context, 14)); 152 | mExpandTextColor = ta.getColor(R.styleable.ExpandLayout_expandTextColor, 0); 153 | mExpandStyle = ta.getInt(R.styleable.ExpandLayout_expandStyle, STYLE_DEFAULT); 154 | mExpandIconWidth = ta.getDimensionPixelSize(R.styleable.ExpandLayout_expandIconWidth, dp2px(context, 15)); 155 | mSpaceMargin = ta.getDimensionPixelSize(R.styleable.ExpandLayout_spaceMargin, dp2px(context, 20)); 156 | mLineSpacingExtra = ta.getDimensionPixelSize(R.styleable.ExpandLayout_lineSpacingExtra, 0); 157 | mLineSpacingMultiplier = ta.getFloat(R.styleable.ExpandLayout_lineSpacingMultiplier, 1.0f); 158 | ta.recycle(); 159 | } 160 | // mMaxLines应该保证大于等于1 161 | if (mMaxLines < 1) { 162 | mMaxLines = 1; 163 | } 164 | } 165 | 166 | /** 167 | * 初始化View 168 | */ 169 | private void initView() { 170 | mRootView = inflate(mContext, R.layout.layout_expand, this); 171 | mTvContent = findViewById(R.id.expand_content_tv); 172 | mLayoutExpandMore = findViewById(R.id.expand_ll); 173 | mIconExpand = findViewById(R.id.expand_iv); 174 | mTvExpand = findViewById(R.id.expand_tv); 175 | mTvExpandHelper = findViewById(R.id.expand_helper_tv); 176 | 177 | mTvExpand.setText(mExpandMoreStr); 178 | mTvContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContentTextSize); 179 | // 辅助TextView,与内容TextView大小相等,保证末尾图标和文字与内容文字居中显示 180 | mTvExpandHelper.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContentTextSize); 181 | mTvExpand.setTextSize(TypedValue.COMPLEX_UNIT_PX, mExpandTextSize); 182 | mTvContent.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier); 183 | mTvExpandHelper.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier); 184 | mTvExpand.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier); 185 | //默认设置展开图标 186 | setExpandMoreIcon(mExpandIconResId); 187 | setContentTextColor(mContentTextColor); 188 | setExpandTextColor(mExpandTextColor); 189 | switch (mExpandStyle) { 190 | case STYLE_ICON: 191 | mIconExpand.setVisibility(VISIBLE); 192 | mTvExpand.setVisibility(GONE); 193 | break; 194 | case STYLE_TEXT: 195 | mIconExpand.setVisibility(GONE); 196 | mTvExpand.setVisibility(VISIBLE); 197 | break; 198 | default: 199 | mIconExpand.setVisibility(VISIBLE); 200 | mTvExpand.setVisibility(VISIBLE); 201 | break; 202 | } 203 | } 204 | 205 | @Override 206 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 207 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 208 | Log.d(TAG, "onMeasure,measureWidth = " + getMeasuredWidth()); 209 | if (mMeasuredWidth <= 0 && getMeasuredWidth() > 0) { 210 | mMeasuredWidth = getMeasuredWidth(); 211 | measureEllipsizeText(mMeasuredWidth); 212 | } 213 | } 214 | 215 | /** 216 | * 设置文本内容 217 | * 218 | * @param contentStr 219 | */ 220 | public void setContent(String contentStr) { 221 | setContent(contentStr, null); 222 | } 223 | 224 | /** 225 | * 设置文本内容 226 | * 227 | * @param contentStr 228 | * @param onExpandStateChangeListener 状态回调监听器 229 | */ 230 | public void setContent(String contentStr, final OnExpandStateChangeListener onExpandStateChangeListener) { 231 | if (TextUtils.isEmpty(contentStr) || mRootView == null) { 232 | return; 233 | } 234 | mOriginContentStr = contentStr; 235 | mOnExpandStateChangeListener = onExpandStateChangeListener; 236 | // 此处需要先设置mTvContent的text属性,防止在列表中,由于没有获取到控件宽度mMeasuredWidth,先执行onMeasure方法测量时,导致文本只能显示一行的问题 237 | // 提前设置好text,再执行onMeasure,则没有该问题 238 | mTvContent.setMaxLines(mMaxLines); 239 | mTvContent.setText(mOriginContentStr); 240 | // 获取文字的宽度 241 | if (mMeasuredWidth <= 0) { 242 | Log.d(TAG, "宽度尚未获取到,第一次加载"); 243 | getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 244 | @Override 245 | public void onGlobalLayout() { 246 | // 用完后立即移除监听,防止多次回调的问题 247 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 248 | getViewTreeObserver().removeOnGlobalLayoutListener(this); 249 | } else { 250 | getViewTreeObserver().removeGlobalOnLayoutListener(this); 251 | } 252 | mMeasuredWidth = getMeasuredWidth(); 253 | Log.d(TAG, "onGlobalLayout,控件宽度 = " + mMeasuredWidth); 254 | measureEllipsizeText(mMeasuredWidth); 255 | } 256 | }); 257 | } else { 258 | Log.d(TAG, "宽度已获取到,非第一次加载"); 259 | measureEllipsizeText(mMeasuredWidth); 260 | } 261 | } 262 | 263 | /** 264 | * 处理文本分行 265 | * 266 | * @param lineWidth 267 | */ 268 | private void measureEllipsizeText(int lineWidth) { 269 | if (TextUtils.isEmpty(mOriginContentStr)) { 270 | return; 271 | } 272 | handleMeasureEllipsizeText(lineWidth); 273 | } 274 | 275 | /** 276 | * 使用StaticLayout处理文本分行和布局 277 | * 278 | * @param lineWidth 文本(布局)宽度 279 | */ 280 | private void handleMeasureEllipsizeText(int lineWidth) { 281 | TextPaint textPaint = mTvContent.getPaint(); 282 | StaticLayout staticLayout = new StaticLayout(mOriginContentStr, textPaint, lineWidth, Layout.Alignment.ALIGN_NORMAL, mLineSpacingMultiplier, mLineSpacingExtra, false); 283 | int lineCount = staticLayout.getLineCount(); 284 | if (lineCount <= mMaxLines) { 285 | // 不足最大行数,直接设置文本 286 | //少于最小展示行数,不再展示更多相关布局 287 | mEllipsizeStr = mOriginContentStr; 288 | mLayoutExpandMore.setVisibility(View.GONE); 289 | mTvContent.setMaxLines(Integer.MAX_VALUE); 290 | mTvContent.setText(mOriginContentStr); 291 | } else { 292 | // 超出最大行数 293 | mRootView.setOnClickListener(this); 294 | mLayoutExpandMore.setVisibility(View.VISIBLE); 295 | // Step1:第mMinLineNum行的处理 296 | handleEllipsizeString(staticLayout, lineWidth); 297 | // Step2:最后一行的处理 298 | handleLastLine(staticLayout, lineWidth); 299 | if (mIsExpand) { 300 | expand(); 301 | } else { 302 | // 默认收缩 303 | collapse(); 304 | } 305 | } 306 | } 307 | 308 | /** 309 | * 处理缩略的字符串 310 | * 311 | * @param staticLayout 312 | * @param lineWidth 313 | */ 314 | private void handleEllipsizeString(StaticLayout staticLayout, int lineWidth) { 315 | if (staticLayout == null) { 316 | return; 317 | } 318 | TextPaint textPaint = mTvContent.getPaint(); 319 | // 获取到第mMinLineNum行的起始和结束位置 320 | int startPos = staticLayout.getLineStart(mMaxLines - 1); 321 | int endPos = staticLayout.getLineEnd(mMaxLines - 1); 322 | Log.d(TAG, "startPos = " + startPos); 323 | Log.d(TAG, "endPos = " + endPos); 324 | // 修正,防止取子串越界 325 | if (startPos < 0) { 326 | startPos = 0; 327 | } 328 | if (endPos > mOriginContentStr.length()) { 329 | endPos = mOriginContentStr.length(); 330 | } 331 | if (startPos > endPos) { 332 | startPos = endPos; 333 | } 334 | String lineContent = mOriginContentStr.substring(startPos, endPos); 335 | float textLength = 0f; 336 | if (lineContent != null) { 337 | textLength = textPaint.measureText(lineContent); 338 | } 339 | Log.d(TAG, "第" + mMaxLines + "行 = " + lineContent); 340 | Log.d(TAG, "第" + mMaxLines + "行 文本长度 = " + textLength); 341 | 342 | String strEllipsizeMark = "..."; 343 | // 展开控件需要预留的长度,预留宽度:"..." + 展开布局与文本间距 +图标长度 + 展开文本长度 344 | float reservedWidth = mSpaceMargin + textPaint.measureText(strEllipsizeMark) + getExpandLayoutReservedWidth(); 345 | Log.d(TAG, "需要预留的长度 = " + reservedWidth); 346 | int correctEndPos = endPos; 347 | if (reservedWidth + textLength > lineWidth) { 348 | // 空间不够,需要按比例截取最后一行的字符串,以确保展示的最后一行文本不会与可展开布局重叠 349 | float exceedSpace = reservedWidth + textLength - lineWidth; 350 | if (textLength != 0) { 351 | correctEndPos = endPos - (int) ((exceedSpace / textLength) * 1.0f * (endPos - startPos)); 352 | } 353 | } 354 | Log.d(TAG, "correctEndPos = " + correctEndPos); 355 | String ellipsizeStr = mOriginContentStr.substring(0, correctEndPos); 356 | mEllipsizeStr = removeEndLineBreak(ellipsizeStr) + strEllipsizeMark; 357 | } 358 | 359 | /** 360 | * 处理最后一行文本 361 | * 362 | * @param staticLayout 363 | * @param lineWidth 364 | */ 365 | private void handleLastLine(StaticLayout staticLayout, int lineWidth) { 366 | if (staticLayout == null) { 367 | return; 368 | } 369 | int lineCount = staticLayout.getLineCount(); 370 | if (lineCount < 1) { 371 | return; 372 | } 373 | int startPos = staticLayout.getLineStart(lineCount - 1); 374 | int endPos = staticLayout.getLineEnd(lineCount - 1); 375 | Log.d(TAG, "最后一行 startPos = " + startPos); 376 | Log.d(TAG, "最后一行 endPos = " + endPos); 377 | // 修正,防止取子串越界 378 | if (startPos < 0) { 379 | startPos = 0; 380 | } 381 | if (endPos > mOriginContentStr.length()) { 382 | endPos = mOriginContentStr.length(); 383 | } 384 | if (startPos > endPos) { 385 | startPos = endPos; 386 | } 387 | String lastLineContent = mOriginContentStr.substring(startPos, endPos); 388 | Log.d(TAG, "最后一行 内容 = " + lastLineContent); 389 | float textLength = 0f; 390 | TextPaint textPaint = mTvContent.getPaint(); 391 | if (lastLineContent != null) { 392 | textLength = textPaint.measureText(lastLineContent); 393 | } 394 | Log.d(TAG, "最后一行 文本长度 = " + textLength); 395 | float reservedWidth = getExpandLayoutReservedWidth(); 396 | if (textLength + reservedWidth > lineWidth) { 397 | //文字宽度+展开布局的宽度 > 一行最大展示宽度 398 | //换行展示“收起”按钮及文字 399 | mOriginContentStr += "\n"; 400 | } 401 | } 402 | 403 | /** 404 | * 获取展开布局的展开收缩控件的预留宽度 405 | * 406 | * @return value = 图标长度 + 展开提示文本长度 407 | */ 408 | private float getExpandLayoutReservedWidth() { 409 | int iconWidth = 0; 410 | if (mExpandStyle == STYLE_DEFAULT || mExpandStyle == STYLE_ICON) { 411 | // ll布局中的内容,图标iv的宽是15dp,参考布局 412 | iconWidth = mExpandIconWidth; 413 | } 414 | float textWidth = 0f; 415 | if (mExpandStyle == STYLE_DEFAULT || mExpandStyle == STYLE_TEXT) { 416 | textWidth = mTvExpand.getPaint().measureText(mExpandMoreStr); 417 | } 418 | // 展开控件需要预留的长度 419 | return iconWidth + textWidth; 420 | } 421 | 422 | /** 423 | * 清除行末换行符 424 | * 425 | * @param text 426 | * @return 427 | */ 428 | private String removeEndLineBreak(CharSequence text) { 429 | if (text == null) { 430 | return null; 431 | } 432 | String str = text.toString(); 433 | if (str.endsWith("\n")) { 434 | str = str.substring(0, str.length() - 1); 435 | } 436 | return str; 437 | } 438 | 439 | /** 440 | * 设置内容文字颜色 441 | */ 442 | public void setContentTextColor(int colorId) { 443 | if (colorId != 0) { 444 | mContentTextColor = colorId; 445 | mTvContent.setTextColor(colorId); 446 | } 447 | } 448 | 449 | /** 450 | * 设置更多/收起文字颜色 451 | */ 452 | public void setExpandTextColor(int colorId) { 453 | if (colorId != 0) { 454 | mExpandTextColor = colorId; 455 | mTvExpand.setTextColor(colorId); 456 | } 457 | } 458 | 459 | /** 460 | * 设置展开更多图标 461 | * 462 | * @param resId 463 | */ 464 | public void setExpandMoreIcon(int resId) { 465 | if (resId != 0) { 466 | mExpandIconResId = resId; 467 | // 当前处于收缩状态时,立即更新图标 468 | if (!mIsExpand) { 469 | mIconExpand.setImageResource(resId); 470 | } 471 | } 472 | } 473 | 474 | /** 475 | * 设置收缩图标 476 | * 477 | * @param resId 478 | */ 479 | public void setCollapseLessIcon(int resId) { 480 | if (resId != 0) { 481 | mCollapseIconResId = resId; 482 | // 当前处于展开状态时,立即更新图标 483 | if (mIsExpand) { 484 | mIconExpand.setImageResource(resId); 485 | } 486 | } 487 | } 488 | 489 | /** 490 | * 展开 491 | */ 492 | public void expand() { 493 | setIsExpand(true); 494 | mTvContent.setMaxLines(Integer.MAX_VALUE); 495 | mTvContent.setText(mOriginContentStr); 496 | mTvExpand.setText(mCollapseLessStr); 497 | if (mCollapseIconResId != 0) { 498 | mIconExpand.setImageResource(mCollapseIconResId); 499 | } 500 | } 501 | 502 | /** 503 | * 收起 504 | */ 505 | public void collapse() { 506 | setIsExpand(false); 507 | mTvContent.setMaxLines(Integer.MAX_VALUE); 508 | mTvContent.setText(mEllipsizeStr); 509 | mTvExpand.setText(mExpandMoreStr); 510 | if (mExpandIconResId != 0) { 511 | mIconExpand.setImageResource(mExpandIconResId); 512 | } 513 | } 514 | 515 | public int getLineCount() { 516 | return mTvContent == null ? 0 : mTvContent.getLineCount(); 517 | } 518 | 519 | public void setShrinkLines(int shrinkLines) { 520 | mMaxLines = shrinkLines; 521 | } 522 | 523 | public void setIsExpand(boolean isExpand) { 524 | mIsExpand = isExpand; 525 | } 526 | 527 | public boolean isExpand() { 528 | return mIsExpand; 529 | } 530 | 531 | /** 532 | * 转换dp为px 533 | * 534 | * @param context 535 | * @param dipValue 536 | * @return 537 | */ 538 | private int dp2px(Context context, float dipValue) { 539 | final float scale = context.getResources().getDisplayMetrics().density; 540 | return (int) (dipValue * scale + 0.5f); 541 | } 542 | 543 | /** 544 | * 转换sp为px 545 | * 546 | * @param context 547 | * @param spValue 548 | * @return 549 | */ 550 | public int sp2px(Context context, float spValue) { 551 | float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 552 | return (int) (spValue * fontScale + 0.5f); 553 | } 554 | 555 | @Override 556 | public void onClick(View v) { 557 | if (!mIsExpand) { 558 | //之前是收缩状态,点击后展开 559 | expand(); 560 | if (mOnExpandStateChangeListener != null) { 561 | mOnExpandStateChangeListener.onExpand(); 562 | } 563 | } else { 564 | //之前是展开状态,点击后收缩 565 | collapse(); 566 | if (mOnExpandStateChangeListener != null) { 567 | mOnExpandStateChangeListener.onCollapse(); 568 | } 569 | } 570 | } 571 | } -------------------------------------------------------------------------------- /.idea/dbnavigator.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 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | --------------------------------------------------------------------------------