├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── styles.xml │ │ │ │ └── attrs.xml │ │ │ ├── drawable │ │ │ │ ├── ic_close.png │ │ │ │ └── ic_choose.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── anim │ │ │ │ ├── dialog_enter_anim.xml │ │ │ │ └── dialog_exit_anim.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ └── layout │ │ │ │ ├── vp_item_address.xml │ │ │ │ ├── layout_tab.xml │ │ │ │ ├── vp_item.xml │ │ │ │ ├── rv_item_address.xml │ │ │ │ ├── activity_main.xml │ │ │ │ └── dialog_choose_address.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── rudy │ │ │ │ └── addresschoose │ │ │ │ ├── bean │ │ │ │ ├── Item.java │ │ │ │ └── Province.java │ │ │ │ ├── utils │ │ │ │ └── FileUtils.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── adapter │ │ │ │ ├── AddressAdapter.java │ │ │ │ └── ViewPagerAdapter.java │ │ │ │ ├── dialog │ │ │ │ └── ChooseAddressDialog.java │ │ │ │ └── widget │ │ │ │ └── SlidingTabLayout.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── rudy │ │ │ └── addresschoose │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── rudy │ │ └── addresschoose │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── screenshot ├── activity.gif └── dialog.gif ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── README.md ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /screenshot/activity.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudyJun/AddressChoose/HEAD/screenshot/activity.gif -------------------------------------------------------------------------------- /screenshot/dialog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudyJun/AddressChoose/HEAD/screenshot/dialog.gif -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 地址选择 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudyJun/AddressChoose/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudyJun/AddressChoose/HEAD/app/src/main/res/drawable/ic_close.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_choose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudyJun/AddressChoose/HEAD/app/src/main/res/drawable/ic_choose.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudyJun/AddressChoose/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudyJun/AddressChoose/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudyJun/AddressChoose/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudyJun/AddressChoose/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudyJun/AddressChoose/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | /.idea 11 | -------------------------------------------------------------------------------- /app/src/main/res/anim/dialog_enter_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/dialog_exit_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AddressChoose 2 | 仿京东地址实现,可弹框 3 | ## Activity 4 | ![Aaron Swartz](https://github.com/RudyJun/AddressChoose/blob/master/screenshot/activity.gif) 5 | ## Dialog 6 | ![Aaron Swartz](https://github.com/RudyJun/AddressChoose/blob/master/screenshot/dialog.gif) 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/rudy/addresschoose/bean/Item.java: -------------------------------------------------------------------------------- 1 | package com.rudy.addresschoose.bean; 2 | 3 | /** 4 | * Created by liuzaijun on 2017/8/16. 5 | */ 6 | 7 | public class Item { 8 | public String itemName; 9 | public boolean isChoose; 10 | 11 | public Item(String itemName) { 12 | this.itemName = itemName; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/test/java/com/rudy/addresschoose/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.rudy.addresschoose; 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() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/vp_item_address.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/rudy/addresschoose/bean/Province.java: -------------------------------------------------------------------------------- 1 | package com.rudy.addresschoose.bean; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by liuzaijun on 2017/8/16. 7 | */ 8 | 9 | public class Province { 10 | 11 | public String name; 12 | public List array; 13 | 14 | public static class City { 15 | public String name; 16 | public List array; 17 | } 18 | 19 | public static class Region { 20 | public String name; 21 | public List array; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/vp_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\AndroidSDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/rudy/addresschoose/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.rudy.addresschoose; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.giveu.addresschoose", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/rv_item_address.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 15 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/rudy/addresschoose/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.rudy.addresschoose.utils; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.InputStreamReader; 7 | 8 | /** 9 | * Created by liuzaijun on 2017/8/16. 10 | */ 11 | 12 | public class FileUtils { 13 | /** 14 | * 从assets目录中读取文件 15 | * 16 | * @param context 17 | * @param fileName 18 | * @return 19 | */ 20 | public static String getFromAssets(Context context, String fileName) { 21 | StringBuffer buffer = new StringBuffer(); 22 | try { 23 | InputStreamReader inputReader = new InputStreamReader(context.getResources().getAssets().open(fileName)); 24 | BufferedReader bufReader = new BufferedReader(inputReader); 25 | String line; 26 | while ((line = bufReader.readLine()) != null) { 27 | buffer.append(line); 28 | } 29 | } catch (Exception e) { 30 | e.printStackTrace(); 31 | } 32 | return buffer.toString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.3" 6 | defaultConfig { 7 | applicationId "com.giveu.addresschoose" 8 | minSdkVersion 14 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | sourceSets { main { res.srcDirs = ['src/main/res', 'src/main/res/anim'] } } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(include: ['*.jar'], dir: 'libs') 25 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 26 | exclude group: 'com.android.support', module: 'support-annotations' 27 | }) 28 | compile 'com.android.support:appcompat-v7:25.3.1' 29 | testCompile 'junit:junit:4.12' 30 | compile 'com.google.code.gson:gson:2.8.0' 31 | compile 'com.android.support:recyclerview-v7:25.0.0' 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 19 | 20 | 34 | 39 | 40 | 41 | 42 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_choose_address.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 21 | 22 | 30 | 31 | 32 | 35 | 36 | 50 | 51 | 56 | 57 | 58 | 59 | 64 | 65 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/rudy/addresschoose/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.rudy.addresschoose; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.view.ViewPager; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | 9 | import com.google.gson.Gson; 10 | import com.google.gson.reflect.TypeToken; 11 | import com.rudy.addresschoose.adapter.ViewPagerAdapter; 12 | import com.rudy.addresschoose.bean.Province; 13 | import com.rudy.addresschoose.dialog.ChooseAddressDialog; 14 | import com.rudy.addresschoose.utils.FileUtils; 15 | import com.rudy.addresschoose.widget.SlidingTabLayout; 16 | 17 | import java.util.ArrayList; 18 | 19 | /** 20 | * Created by liuzaijun on 2017/8/17. 21 | */ 22 | public class MainActivity extends AppCompatActivity { 23 | 24 | private ViewPager vpAddress; 25 | private SlidingTabLayout stCity; 26 | private ViewPagerAdapter pagerAdapter; 27 | private ArrayList provinces; 28 | private TextView tvAddress; 29 | private ChooseAddressDialog chooseAddressDialog; 30 | 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_main); 36 | vpAddress = (ViewPager) findViewById(R.id.vp_address); 37 | tvAddress = (TextView) findViewById(R.id.tv_address); 38 | stCity = (SlidingTabLayout) findViewById(R.id.st_city); 39 | pagerAdapter = new ViewPagerAdapter(this, vpAddress); 40 | vpAddress.setAdapter(pagerAdapter); 41 | vpAddress.setOffscreenPageLimit(3); 42 | stCity.setViewPager(vpAddress); 43 | 44 | chooseAddressDialog = new ChooseAddressDialog(this); 45 | chooseAddressDialog.setOnAddressChooseListener(new ChooseAddressDialog.OnAddressChooseListener() { 46 | @Override 47 | public void onChoose(String province, String city, String region, String street) { 48 | tvAddress.setText(province + city + region + street + "(可点击)"); 49 | } 50 | }); 51 | tvAddress.setOnClickListener(new View.OnClickListener() { 52 | @Override 53 | public void onClick(View v) { 54 | chooseAddressDialog.show(); 55 | } 56 | }); 57 | 58 | //解析地址 59 | getAddressJson(); 60 | pagerAdapter.setOnAddressChooseListener(new ViewPagerAdapter.OnAddressChooseListener() { 61 | @Override 62 | public void onChoose(String province, String city, String region, String street) { 63 | tvAddress.setText(province + city + region + street + "(可点击)"); 64 | } 65 | }); 66 | } 67 | 68 | public void getAddressJson() { 69 | //开启子线程处理读取地址并解析 70 | new Thread(new Runnable() { 71 | @Override 72 | public void run() { 73 | Gson gson = new Gson(); 74 | provinces = gson.fromJson(FileUtils.getFromAssets(MainActivity.this, "addressJson.json"), new TypeToken>() { 75 | }.getType()); 76 | initView(); 77 | } 78 | }).start(); 79 | } 80 | 81 | private void initView() { 82 | runOnUiThread(new Runnable() { 83 | @Override 84 | public void run() { 85 | //pagerAdapter、chooseAddressDialog初始化数据 86 | processData(); 87 | } 88 | }); 89 | } 90 | 91 | private void processData() { 92 | pagerAdapter.setData(provinces); 93 | chooseAddressDialog.setData(provinces); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/rudy/addresschoose/adapter/AddressAdapter.java: -------------------------------------------------------------------------------- 1 | package com.rudy.addresschoose.adapter; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.rudy.addresschoose.R; 13 | import com.rudy.addresschoose.bean.Item; 14 | 15 | import java.util.ArrayList; 16 | 17 | /** 18 | * Created by liuzaijun on 2017/8/17. 19 | */ 20 | 21 | public class AddressAdapter extends RecyclerView.Adapter { 22 | 23 | private Context context; 24 | private ArrayList items; 25 | 26 | public AddressAdapter(Context context, ArrayList items) { 27 | this.context = context; 28 | this.items = items; 29 | } 30 | 31 | @Override 32 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 33 | View itemView = LayoutInflater.from(context).inflate(R.layout.rv_item_address, parent, false); 34 | return new ViewHolder(itemView); 35 | } 36 | 37 | @Override 38 | public void onBindViewHolder(final ViewHolder holder, final int position) { 39 | holder.tvAddress.setText(items.get(position).itemName); 40 | if (items.get(position).isChoose) { 41 | holder.ivChoose.setVisibility(View.VISIBLE); 42 | holder.tvAddress.setTextColor(Color.parseColor("#ff3e3e")); 43 | } else { 44 | holder.ivChoose.setVisibility(View.GONE); 45 | holder.tvAddress.setTextColor(Color.parseColor("#4a4a4a")); 46 | } 47 | 48 | holder.getConvertView().setOnClickListener(new View.OnClickListener() { 49 | @Override 50 | public void onClick(View v) { 51 | //处理当前的点击事件,即勾选地址 52 | holder.ivChoose.setVisibility(View.VISIBLE); 53 | items.get(position).isChoose = true; 54 | for (Item item : items) { 55 | if (!item.itemName.equals(items.get(position).itemName) && item.isChoose) { 56 | item.isChoose = false; 57 | break; 58 | } 59 | } 60 | notifyDataSetChanged(); 61 | if (listener != null) { 62 | listener.onItemClick(items.get(position).itemName, position); 63 | } 64 | } 65 | }); 66 | 67 | 68 | } 69 | 70 | @Override 71 | public int getItemCount() { 72 | return items.size(); 73 | } 74 | 75 | private OnItemClikListener listener; 76 | 77 | /** 78 | * 点击item的回调 79 | * @param listener 80 | */ 81 | public void setOnItemClikListener(OnItemClikListener listener) { 82 | this.listener = listener; 83 | } 84 | 85 | public interface OnItemClikListener { 86 | void onItemClick(String itemName, int addressPos); 87 | } 88 | 89 | public static class ViewHolder extends RecyclerView.ViewHolder { 90 | public TextView tvAddress; 91 | public ImageView ivChoose; 92 | 93 | public ViewHolder(View itemView) { 94 | super(itemView); 95 | tvAddress = (TextView) itemView.findViewById(R.id.tv_address); 96 | ivChoose = (ImageView) itemView.findViewById(R.id.iv_choose); 97 | } 98 | 99 | public View getConvertView() { 100 | return itemView; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/rudy/addresschoose/dialog/ChooseAddressDialog.java: -------------------------------------------------------------------------------- 1 | package com.rudy.addresschoose.dialog; 2 | 3 | import android.app.Activity; 4 | import android.app.Dialog; 5 | import android.support.v4.view.ViewPager; 6 | import android.view.Gravity; 7 | import android.view.View; 8 | import android.view.Window; 9 | import android.view.WindowManager; 10 | import android.widget.ImageView; 11 | 12 | import com.rudy.addresschoose.R; 13 | import com.rudy.addresschoose.adapter.ViewPagerAdapter; 14 | import com.rudy.addresschoose.bean.Province; 15 | import com.rudy.addresschoose.widget.SlidingTabLayout; 16 | 17 | import java.util.ArrayList; 18 | 19 | /** 20 | * Created by liuzaijun on 2017/8/19. 21 | */ 22 | 23 | public class ChooseAddressDialog extends Dialog { 24 | protected Activity mAttachActivity; 25 | private ViewPager vpAddress; 26 | private SlidingTabLayout stCity; 27 | private ViewPagerAdapter pagerAdapter; 28 | private ImageView ivClose; 29 | 30 | public ChooseAddressDialog(Activity context) { 31 | this(context, R.style.customerDialog); 32 | } 33 | 34 | public ChooseAddressDialog(Activity context, int themeResId) { 35 | super(context, themeResId); 36 | mAttachActivity = context; 37 | init(); 38 | } 39 | 40 | private void init() { 41 | setContentView(R.layout.dialog_choose_address); 42 | vpAddress = (ViewPager) findViewById(R.id.vp_address); 43 | stCity = (SlidingTabLayout) findViewById(R.id.st_city); 44 | ivClose = (ImageView) findViewById(R.id.iv_close); 45 | ivClose.setOnClickListener(new View.OnClickListener() { 46 | @Override 47 | public void onClick(View v) { 48 | dismiss(); 49 | } 50 | }); 51 | pagerAdapter = new ViewPagerAdapter(mAttachActivity, vpAddress); 52 | vpAddress.setAdapter(pagerAdapter); 53 | vpAddress.setOffscreenPageLimit(3); 54 | stCity.setViewPager(vpAddress); 55 | //pagerAdapter的回调 56 | pagerAdapter.setOnAddressChooseListener(new ViewPagerAdapter.OnAddressChooseListener() { 57 | @Override 58 | public void onChoose(String province, String city, String region, String street) { 59 | if (listener != null) { 60 | //dialog的回调 61 | listener.onChoose(province, city, region, street); 62 | } 63 | //延迟消失时为了显示选择完成时可以看到选择了哪项 64 | vpAddress.postDelayed(new Runnable() { 65 | @Override 66 | public void run() { 67 | dismiss(); 68 | } 69 | }, 100); 70 | } 71 | }); 72 | 73 | Window window = getWindow(); 74 | if (window != null) { 75 | WindowManager.LayoutParams attributes = window.getAttributes(); 76 | attributes.height = (int) (getScreenHeight() * (0.56)); 77 | attributes.width = getScreenWidth(); 78 | attributes.gravity = Gravity.BOTTOM; 79 | window.setAttributes(attributes); 80 | window.setWindowAnimations(R.style.dialogWindowAnim); //设置窗口弹出动画 81 | } 82 | setCancelable(false); 83 | setCanceledOnTouchOutside(false); 84 | } 85 | 86 | /** 87 | * 初始化数据 88 | * 89 | * @param provinces 90 | */ 91 | public void setData(ArrayList provinces) { 92 | pagerAdapter.setData(provinces); 93 | } 94 | 95 | private OnAddressChooseListener listener; 96 | 97 | /** 98 | * 地址选择完成的回调 99 | * 100 | * @param listener 101 | */ 102 | public void setOnAddressChooseListener(OnAddressChooseListener listener) { 103 | this.listener = listener; 104 | } 105 | 106 | public interface OnAddressChooseListener { 107 | void onChoose(String province, String city, String region, String street); 108 | } 109 | 110 | /** 111 | * 获取屏幕宽度 112 | */ 113 | public int getScreenWidth() { 114 | return mAttachActivity.getResources().getDisplayMetrics().widthPixels; 115 | } 116 | 117 | /** 118 | * 获取屏幕高度 119 | */ 120 | public int getScreenHeight() { 121 | return mAttachActivity.getResources().getDisplayMetrics().heightPixels; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /app/src/main/java/com/rudy/addresschoose/adapter/ViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.rudy.addresschoose.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.PagerAdapter; 5 | import android.support.v4.view.ViewPager; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import com.rudy.addresschoose.R; 12 | import com.rudy.addresschoose.bean.Item; 13 | import com.rudy.addresschoose.bean.Province; 14 | 15 | import java.util.ArrayList; 16 | /** 17 | * Created by liuzaijun on 2017/8/17. 18 | */ 19 | public class ViewPagerAdapter extends PagerAdapter { 20 | 21 | private ArrayList tabList; 22 | private Context context; 23 | private ArrayList provinces; 24 | private ArrayList> itemList; 25 | private ViewPager mViewPager; 26 | private int pPosition = -1; 27 | private int cPosition = -1; 28 | private int rPosition = -1; 29 | 30 | public ViewPagerAdapter(Context context, ViewPager viewPager) { 31 | this.context = context; 32 | this.tabList = new ArrayList<>(); 33 | this.itemList = new ArrayList<>(); 34 | this.mViewPager = viewPager; 35 | if (viewPager == null) { 36 | throw new IllegalArgumentException("viewPager is null , please check"); 37 | } 38 | } 39 | 40 | /** 41 | * 省列表,其中每一个省下又有市、区、街道(可能有也可能没有) 42 | * 43 | * @param provinces 44 | */ 45 | public void setData(ArrayList provinces) { 46 | if (provinces == null) { 47 | return; 48 | } 49 | this.provinces = provinces; 50 | ArrayList provinceItem = new ArrayList<>(); 51 | //遍历provinces,得到省 52 | for (Province province : provinces) { 53 | Item item = new Item(province.name); 54 | provinceItem.add(item); 55 | } 56 | //添加省列表 57 | itemList.add(provinceItem); 58 | //添加一个tab 59 | tabList.add("请选择"); 60 | //调用notifyDataSetChanged()方法,以便刷新tab的内容(最开始tab和viewpager是没有内容的) 61 | notifyDataSetChanged(); 62 | } 63 | 64 | @Override 65 | public int getCount() { 66 | return tabList.size(); 67 | } 68 | 69 | @Override 70 | public boolean isViewFromObject(View view, Object object) { 71 | return view == object; 72 | } 73 | 74 | @Override 75 | public void destroyItem(ViewGroup container, int position, Object object) { 76 | container.removeView((View) object); 77 | } 78 | 79 | @Override 80 | public View instantiateItem(ViewGroup container, final int position) { 81 | View view = View.inflate(context, R.layout.vp_item_address, null); 82 | view.setTag(position); 83 | //rvAddress用来填充具体的省、市、区、街道(可能是没有的,得看选中的地区) 84 | RecyclerView rvAddress = (RecyclerView) view.findViewById(R.id.rv_address); 85 | //布局管理器 86 | LinearLayoutManager layoutManager = new LinearLayoutManager(context); 87 | layoutManager.setOrientation(LinearLayoutManager.VERTICAL); 88 | rvAddress.setLayoutManager(layoutManager); 89 | //适配器 90 | AddressAdapter addressAdapter = new AddressAdapter(context, itemList.get(position)); 91 | rvAddress.setAdapter(addressAdapter); 92 | addressAdapter.setOnItemClikListener(new AddressAdapter.OnItemClikListener() { 93 | @Override 94 | public void onItemClick(String itemName, int addressPos) { 95 | //替换tab的内容,如原来显示的tab是请选择,选中广东省后,替换position为0所对应的string为广东省 96 | replaceStr(position, itemName); 97 | //这个list用来存储下一级列表的内容,如广东省下的市 98 | ArrayList cityItem = new ArrayList<>(); 99 | //标识此次选择的省、市、区、街道与上次选择的是否一样 100 | boolean isChange = false; 101 | switch (position) { 102 | case 0: 103 | //根据选中的位置判断是否选中的是同一个省或同一个市...其他case也是如此 104 | if (addressPos != pPosition) { 105 | //此次选择与上次不同,需要截断之后的内容,如上次选择广东省,这次选择北京 106 | //那么需要截断广东省后的市、区、街道,即删除省之后的选择界面,从重置省之后的选中position 107 | truncateList(position + 1); 108 | isChange = true; 109 | cPosition = -1; 110 | rPosition = -1; 111 | } 112 | pPosition = addressPos;//记录该次选择省的位置 113 | //遍历得到该省下的市级列表 114 | for (Province.City city : provinces.get(pPosition).array) { 115 | Item item = new Item(city.name); 116 | cityItem.add(item); 117 | } 118 | break; 119 | case 1: 120 | if (addressPos != cPosition) { 121 | truncateList(position + 1); 122 | isChange = true; 123 | rPosition = -1; 124 | } 125 | cPosition = addressPos; 126 | for (Province.Region region : provinces.get(pPosition).array.get(cPosition).array) { 127 | Item item = new Item(region.name); 128 | cityItem.add(item); 129 | } 130 | break; 131 | case 2: 132 | if (addressPos != rPosition) { 133 | truncateList(position + 1); 134 | isChange = true; 135 | } 136 | rPosition = addressPos; 137 | //该区是否有街道的列表 138 | if (provinces.get(pPosition).array.get(cPosition).array.get(rPosition).array != null 139 | && !provinces.get(pPosition).array.get(cPosition).array.get(rPosition).array.isEmpty()) { 140 | for (String street : provinces.get(pPosition).array.get(cPosition).array.get(rPosition).array) { 141 | Item item = new Item(street); 142 | cityItem.add(item); 143 | } 144 | } 145 | break; 146 | case 3: 147 | break; 148 | } 149 | dealDataAndScroll(isChange, cityItem, position + 1); 150 | } 151 | }); 152 | container.addView(view); 153 | return view; 154 | } 155 | 156 | private void truncateList(int fromIndex) { 157 | if (tabList.size() > fromIndex) { 158 | tabList.subList(fromIndex, tabList.size()).clear(); 159 | itemList.subList(fromIndex, itemList.size()).clear(); 160 | } 161 | } 162 | 163 | /** 164 | * 替换该postion对应的string 165 | * 166 | * @param position 167 | * @param itemName 168 | */ 169 | private void replaceStr(int position, String itemName) { 170 | tabList.remove(position); 171 | tabList.add(position, itemName); 172 | } 173 | 174 | /** 175 | * @param isChange 与上次的选择是否一致 176 | * @param cityItem 存储下一级地区的list 177 | * @param position 需要滑动至viewpager的哪个位置 178 | */ 179 | private void dealDataAndScroll(boolean isChange, ArrayList cityItem, final int position) { 180 | //判断cityItem是否为空,不为空说明还有下一级列表,否则说明地区选择完成 181 | if (!cityItem.isEmpty()) { 182 | //如果与上次选择不一致,之前已经做了内容的清除,现在新增一个tab 183 | if (isChange) { 184 | tabList.add("请选择"); 185 | itemList.add(cityItem); 186 | //通知adapter,数据源已发生改变,需要刷新界面 187 | notifyDataSetChanged(); 188 | } 189 | //延时滑动,以便有简短的事件可以看到选中的项 190 | mViewPager.postDelayed(new Runnable() { 191 | @Override 192 | public void run() { 193 | mViewPager.setCurrentItem(position); 194 | } 195 | }, 100); 196 | } else { 197 | notifyDataSetChanged(); 198 | //地址选择完成 199 | if (listener != null) { 200 | String province = ""; 201 | String city = ""; 202 | String region = ""; 203 | String street = ""; 204 | for (int i = 0; i < tabList.size(); i++) { 205 | String str = tabList.get(i); 206 | switch (i) { 207 | case 0: 208 | province = str; 209 | break; 210 | case 1: 211 | city = str; 212 | 213 | break; 214 | case 2: 215 | region = str; 216 | 217 | break; 218 | case 3: 219 | street = str; 220 | break; 221 | } 222 | } 223 | listener.onChoose(province, city, region, street); 224 | } 225 | } 226 | } 227 | 228 | private OnAddressChooseListener listener; 229 | 230 | /** 231 | * 地址选择完成的回调 232 | * 233 | * @param listener 234 | */ 235 | public void setOnAddressChooseListener(OnAddressChooseListener listener) { 236 | this.listener = listener; 237 | } 238 | 239 | public interface OnAddressChooseListener { 240 | void onChoose(String province, String city, String region, String street); 241 | } 242 | 243 | @Override 244 | public int getItemPosition(Object object) { 245 | View view = (View) object; 246 | int position = (int) view.getTag(); 247 | //viewpager的局部刷新,想了解原理看请源码或资料 248 | if (position > mViewPager.getCurrentItem()) { 249 | //需要重绘 250 | return POSITION_NONE; 251 | } else { 252 | //不需要重绘 253 | return POSITION_UNCHANGED; 254 | } 255 | } 256 | 257 | @Override 258 | public CharSequence getPageTitle(int position) { 259 | return tabList.get(position); 260 | } 261 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rudy/addresschoose/widget/SlidingTabLayout.java: -------------------------------------------------------------------------------- 1 | package com.rudy.addresschoose.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.database.DataSetObserver; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.Rect; 11 | import android.graphics.drawable.GradientDrawable; 12 | import android.os.Bundle; 13 | import android.os.Parcelable; 14 | import android.support.annotation.Nullable; 15 | import android.support.v4.app.Fragment; 16 | import android.support.v4.app.FragmentActivity; 17 | import android.support.v4.app.FragmentManager; 18 | import android.support.v4.app.FragmentPagerAdapter; 19 | import android.support.v4.view.PagerAdapter; 20 | import android.support.v4.view.ViewPager; 21 | import android.util.AttributeSet; 22 | import android.util.SparseArray; 23 | import android.util.TypedValue; 24 | import android.view.Gravity; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.widget.HorizontalScrollView; 28 | import android.widget.LinearLayout; 29 | import android.widget.TextView; 30 | 31 | import com.rudy.addresschoose.R; 32 | 33 | import java.util.ArrayList; 34 | import java.util.Collections; 35 | 36 | /** 37 | * 滑动TabLayout,对于ViewPager的依赖性强 38 | */ 39 | public class SlidingTabLayout extends HorizontalScrollView implements ViewPager.OnPageChangeListener { 40 | private Context mContext; 41 | private ViewPager mViewPager; 42 | private ArrayList mTitles; 43 | private LinearLayout mTabsContainer; 44 | private int mCurrentTab; 45 | private float mCurrentPositionOffset; 46 | private int mTabCount; 47 | /** 48 | * 用于绘制显示器 49 | */ 50 | private Rect mIndicatorRect = new Rect(); 51 | /** 52 | * 用于实现滚动居中 53 | */ 54 | private Rect mTabRect = new Rect(); 55 | private GradientDrawable mIndicatorDrawable = new GradientDrawable(); 56 | 57 | private Paint mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 58 | private Paint mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 59 | private Paint mTrianglePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 60 | private Path mTrianglePath = new Path(); 61 | private static final int STYLE_NORMAL = 0; 62 | private static final int STYLE_TRIANGLE = 1; 63 | private static final int STYLE_BLOCK = 2; 64 | private int mIndicatorStyle = STYLE_NORMAL; 65 | 66 | private float mTabPadding; 67 | private boolean mTabSpaceEqual; 68 | private float mTabWidth; 69 | 70 | /** 71 | * indicator 72 | */ 73 | private int mIndicatorColor; 74 | private float mIndicatorHeight; 75 | private float mIndicatorWidth; 76 | private float mIndicatorCornerRadius; 77 | private float mIndicatorMarginLeft; 78 | private float mIndicatorMarginTop; 79 | private float mIndicatorMarginRight; 80 | private float mIndicatorMarginBottom; 81 | private int mIndicatorGravity; 82 | private boolean mIndicatorWidthEqualTitle; 83 | 84 | /** 85 | * underline 86 | */ 87 | private int mUnderlineColor; 88 | private float mUnderlineHeight; 89 | private int mUnderlineGravity; 90 | 91 | /** 92 | * divider 93 | */ 94 | private int mDividerColor; 95 | private float mDividerWidth; 96 | private float mDividerPadding; 97 | 98 | /** 99 | * title 100 | */ 101 | private static final int TEXT_BOLD_NONE = 0; 102 | private static final int TEXT_BOLD_WHEN_SELECT = 1; 103 | private static final int TEXT_BOLD_BOTH = 2; 104 | private float mTextsize; 105 | private int mTextSelectColor; 106 | private int mTextUnselectColor; 107 | private int mTextBold; 108 | private boolean mTextAllCaps; 109 | 110 | private int mLastScrollX; 111 | private int mHeight; 112 | private boolean mSnapOnTabClick; 113 | 114 | public SlidingTabLayout(Context context) { 115 | this(context, null, 0); 116 | } 117 | 118 | public SlidingTabLayout(Context context, AttributeSet attrs) { 119 | this(context, attrs, 0); 120 | } 121 | 122 | public SlidingTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { 123 | super(context, attrs, defStyleAttr); 124 | setFillViewport(true);//设置滚动视图是否可以伸缩其内容以填充视口 125 | setWillNotDraw(false);//重写onDraw方法,需要调用这个方法来清除flag 126 | setClipChildren(false); 127 | setClipToPadding(false); 128 | 129 | this.mContext = context; 130 | mTabsContainer = new LinearLayout(context); 131 | addView(mTabsContainer); 132 | 133 | obtainAttributes(context, attrs); 134 | 135 | //get layout_height 136 | String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height"); 137 | 138 | if (height.equals(ViewGroup.LayoutParams.MATCH_PARENT + "")) { 139 | } else if (height.equals(ViewGroup.LayoutParams.WRAP_CONTENT + "")) { 140 | } else { 141 | int[] systemAttrs = {android.R.attr.layout_height}; 142 | TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs); 143 | mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT); 144 | a.recycle(); 145 | } 146 | } 147 | 148 | private void obtainAttributes(Context context, AttributeSet attrs) { 149 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingTabLayout); 150 | 151 | mIndicatorStyle = ta.getInt(R.styleable.SlidingTabLayout_tl_indicator_style, STYLE_NORMAL); 152 | mIndicatorColor = ta.getColor(R.styleable.SlidingTabLayout_tl_indicator_color, Color.parseColor(mIndicatorStyle == STYLE_BLOCK ? "#4B6A87" : "#ffffff")); 153 | mIndicatorHeight = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_height, 154 | dp2px(mIndicatorStyle == STYLE_TRIANGLE ? 4 : (mIndicatorStyle == STYLE_BLOCK ? -1 : 2))); 155 | mIndicatorWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_width, dp2px(mIndicatorStyle == STYLE_TRIANGLE ? 10 : -1)); 156 | mIndicatorCornerRadius = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_corner_radius, dp2px(mIndicatorStyle == STYLE_BLOCK ? -1 : 0)); 157 | mIndicatorMarginLeft = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_left, dp2px(0)); 158 | mIndicatorMarginTop = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_top, dp2px(mIndicatorStyle == STYLE_BLOCK ? 7 : 0)); 159 | mIndicatorMarginRight = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_right, dp2px(0)); 160 | mIndicatorMarginBottom = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_bottom, dp2px(mIndicatorStyle == STYLE_BLOCK ? 7 : 0)); 161 | mIndicatorGravity = ta.getInt(R.styleable.SlidingTabLayout_tl_indicator_gravity, Gravity.BOTTOM); 162 | mIndicatorWidthEqualTitle = ta.getBoolean(R.styleable.SlidingTabLayout_tl_indicator_width_equal_title, false); 163 | 164 | mUnderlineColor = ta.getColor(R.styleable.SlidingTabLayout_tl_underline_color, Color.parseColor("#ffffff")); 165 | mUnderlineHeight = ta.getDimension(R.styleable.SlidingTabLayout_tl_underline_height, dp2px(0)); 166 | mUnderlineGravity = ta.getInt(R.styleable.SlidingTabLayout_tl_underline_gravity, Gravity.BOTTOM); 167 | 168 | mDividerColor = ta.getColor(R.styleable.SlidingTabLayout_tl_divider_color, Color.parseColor("#ffffff")); 169 | mDividerWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_divider_width, dp2px(0)); 170 | mDividerPadding = ta.getDimension(R.styleable.SlidingTabLayout_tl_divider_padding, dp2px(12)); 171 | 172 | mTextsize = ta.getDimension(R.styleable.SlidingTabLayout_tl_textsize, sp2px(14)); 173 | mTextSelectColor = ta.getColor(R.styleable.SlidingTabLayout_tl_textSelectColor, Color.parseColor("#ffffff")); 174 | mTextUnselectColor = ta.getColor(R.styleable.SlidingTabLayout_tl_textUnselectColor, Color.parseColor("#AAffffff")); 175 | mTextBold = ta.getInt(R.styleable.SlidingTabLayout_tl_textBold, TEXT_BOLD_NONE); 176 | mTextAllCaps = ta.getBoolean(R.styleable.SlidingTabLayout_tl_textAllCaps, false); 177 | 178 | mTabSpaceEqual = ta.getBoolean(R.styleable.SlidingTabLayout_tl_tab_space_equal, false); 179 | mTabWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_tab_width, dp2px(-1)); 180 | mTabPadding = ta.getDimension(R.styleable.SlidingTabLayout_tl_tab_padding, mTabSpaceEqual || mTabWidth > 0 ? dp2px(0) : dp2px(20)); 181 | 182 | ta.recycle(); 183 | } 184 | 185 | /** 186 | * 关联ViewPager 187 | */ 188 | public void setViewPager(ViewPager vp) { 189 | if (vp == null || vp.getAdapter() == null) { 190 | throw new IllegalStateException("ViewPager or ViewPager adapter can not be NULL !"); 191 | } 192 | 193 | this.mViewPager = vp; 194 | 195 | this.mViewPager.removeOnPageChangeListener(this); 196 | this.mViewPager.addOnPageChangeListener(this); 197 | PagerAdapter adapter = this.mViewPager.getAdapter(); 198 | setPagerAdapter(adapter, true); 199 | } 200 | 201 | private PagerAdapter mPagerAdapter; 202 | private DataSetObserver mPagerAdapterObserver; 203 | 204 | private void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) { 205 | if (mPagerAdapter != null && mPagerAdapterObserver != null) { 206 | // If we already have a PagerAdapter, unregister our observer 207 | mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver); 208 | } 209 | 210 | mPagerAdapter = adapter; 211 | 212 | if (addObserver && adapter != null) { 213 | // Register our observer on the new adapter 214 | if (mPagerAdapterObserver == null) { 215 | mPagerAdapterObserver = new PagerAdapterObserver(); 216 | } 217 | adapter.registerDataSetObserver(mPagerAdapterObserver); 218 | } 219 | 220 | // Finally make sure we reflect the new adapter 221 | notifyDataSetChanged(); 222 | } 223 | 224 | private class PagerAdapterObserver extends DataSetObserver { 225 | PagerAdapterObserver() { 226 | } 227 | 228 | @Override 229 | public void onChanged() { 230 | notifyDataSetChanged(); 231 | } 232 | 233 | @Override 234 | public void onInvalidated() { 235 | notifyDataSetChanged(); 236 | } 237 | } 238 | 239 | /** 240 | * 关联ViewPager,用于不想在ViewPager适配器中设置titles数据的情况 241 | */ 242 | public void setViewPager(ViewPager vp, String[] titles) { 243 | if (vp == null || vp.getAdapter() == null) { 244 | throw new IllegalStateException("ViewPager or ViewPager adapter can not be NULL !"); 245 | } 246 | 247 | if (titles == null || titles.length == 0) { 248 | throw new IllegalStateException("Titles can not be EMPTY !"); 249 | } 250 | 251 | if (titles.length != vp.getAdapter().getCount()) { 252 | throw new IllegalStateException("Titles length must be the same as the page count !"); 253 | } 254 | 255 | this.mViewPager = vp; 256 | mTitles = new ArrayList<>(); 257 | Collections.addAll(mTitles, titles); 258 | 259 | this.mViewPager.removeOnPageChangeListener(this); 260 | this.mViewPager.addOnPageChangeListener(this); 261 | notifyDataSetChanged(); 262 | } 263 | 264 | /** 265 | * 关联ViewPager,用于连适配器都不想自己实例化的情况 266 | */ 267 | public void setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList fragments) { 268 | if (vp == null) { 269 | throw new IllegalStateException("ViewPager can not be NULL !"); 270 | } 271 | 272 | if (titles == null || titles.length == 0) { 273 | throw new IllegalStateException("Titles can not be EMPTY !"); 274 | } 275 | 276 | this.mViewPager = vp; 277 | this.mViewPager.setAdapter(new InnerPagerAdapter(fa.getSupportFragmentManager(), fragments, titles)); 278 | 279 | this.mViewPager.removeOnPageChangeListener(this); 280 | this.mViewPager.addOnPageChangeListener(this); 281 | notifyDataSetChanged(); 282 | } 283 | 284 | /** 285 | * 更新数据 286 | */ 287 | public void notifyDataSetChanged() { 288 | mTabsContainer.removeAllViews(); 289 | this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size(); 290 | View tabView; 291 | for (int i = 0; i < mTabCount; i++) { 292 | tabView = View.inflate(mContext, R.layout.layout_tab, null); 293 | CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(i) : mTitles.get(i); 294 | addTab(i, pageTitle.toString(), tabView); 295 | } 296 | updateTabStyles(); 297 | post(new Runnable() { 298 | @Override 299 | public void run() { 300 | fullScroll(FOCUS_RIGHT); 301 | } 302 | }); 303 | } 304 | 305 | public void addNewTab(String title) { 306 | View tabView = View.inflate(mContext, R.layout.layout_tab, null); 307 | if (mTitles != null) { 308 | mTitles.add(title); 309 | } 310 | 311 | CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(mTabCount) : mTitles.get(mTabCount); 312 | addTab(mTabCount, pageTitle.toString(), tabView); 313 | this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size(); 314 | 315 | updateTabStyles(); 316 | } 317 | 318 | /** 319 | * 创建并添加tab 320 | */ 321 | private void addTab(final int position, String title, View tabView) { 322 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 323 | if (tv_tab_title != null) { 324 | if (title != null) tv_tab_title.setText(title); 325 | } 326 | 327 | tabView.setOnClickListener(new OnClickListener() { 328 | @Override 329 | public void onClick(View v) { 330 | int position = mTabsContainer.indexOfChild(v); 331 | if (position != -1) { 332 | if (mViewPager.getCurrentItem() != position) { 333 | if (mSnapOnTabClick) { 334 | mViewPager.setCurrentItem(position, false); 335 | } else { 336 | mViewPager.setCurrentItem(position); 337 | } 338 | 339 | if (mListener != null) { 340 | mListener.onTabSelect(position); 341 | } 342 | } else { 343 | if (mListener != null) { 344 | mListener.onTabReselect(position); 345 | } 346 | } 347 | } 348 | } 349 | }); 350 | 351 | /** 每一个Tab的布局参数 */ 352 | LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ? 353 | new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) : 354 | new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 355 | if (mTabWidth > 0) { 356 | lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT); 357 | } 358 | 359 | mTabsContainer.addView(tabView, position, lp_tab); 360 | } 361 | 362 | private void updateTabStyles() { 363 | for (int i = 0; i < mTabCount; i++) { 364 | View v = mTabsContainer.getChildAt(i); 365 | // v.setPadding((int) mTabPadding, v.getPaddingTop(), (int) mTabPadding, v.getPaddingBottom()); 366 | TextView tv_tab_title = (TextView) v.findViewById(R.id.tv_tab_title); 367 | if (tv_tab_title != null) { 368 | tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor); 369 | tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize); 370 | tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); 371 | if (mTextAllCaps) { 372 | tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase()); 373 | } 374 | 375 | if (mTextBold == TEXT_BOLD_BOTH) { 376 | tv_tab_title.getPaint().setFakeBoldText(true); 377 | } else if (mTextBold == TEXT_BOLD_NONE) { 378 | tv_tab_title.getPaint().setFakeBoldText(false); 379 | } 380 | } 381 | } 382 | } 383 | 384 | @Override 385 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 386 | /** 387 | * position:当前View的位置 388 | * mCurrentPositionOffset:当前View的偏移量比例.[0,1) 389 | */ 390 | this.mCurrentTab = position; 391 | this.mCurrentPositionOffset = positionOffset; 392 | scrollToCurrentTab(); 393 | invalidate(); 394 | } 395 | 396 | @Override 397 | public void onPageSelected(int position) { 398 | updateTabSelection(position); 399 | } 400 | 401 | @Override 402 | public void onPageScrollStateChanged(int state) { 403 | } 404 | 405 | /** 406 | * HorizontalScrollView滚到当前tab,并且居中显示 407 | */ 408 | private void scrollToCurrentTab() { 409 | if (mTabCount <= 0) { 410 | return; 411 | } 412 | 413 | int offset = (int) (mCurrentPositionOffset * mTabsContainer.getChildAt(mCurrentTab).getWidth()); 414 | /**当前Tab的left+当前Tab的Width乘以positionOffset*/ 415 | int newScrollX = mTabsContainer.getChildAt(mCurrentTab).getLeft() + offset; 416 | 417 | if (mCurrentTab > 0 || offset > 0) { 418 | /**HorizontalScrollView移动到当前tab,并居中*/ 419 | newScrollX -= getWidth() / 2 - getPaddingLeft(); 420 | calcIndicatorRect(); 421 | newScrollX += ((mTabRect.right - mTabRect.left) / 2); 422 | } 423 | 424 | if (newScrollX != mLastScrollX) { 425 | mLastScrollX = newScrollX; 426 | /** scrollTo(int x,int y):x,y代表的不是坐标点,而是偏移量 427 | * x:表示离起始位置的x水平方向的偏移量 428 | * y:表示离起始位置的y垂直方向的偏移量 429 | */ 430 | scrollTo(newScrollX, 0); 431 | } 432 | } 433 | 434 | private void updateTabSelection(int position) { 435 | for (int i = 0; i < mTabCount; ++i) { 436 | View tabView = mTabsContainer.getChildAt(i); 437 | final boolean isSelect = i == position; 438 | TextView tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 439 | 440 | if (tab_title != null) { 441 | tab_title.setTextColor(isSelect ? mTextSelectColor : mTextUnselectColor); 442 | if (mTextBold == TEXT_BOLD_WHEN_SELECT) { 443 | tab_title.getPaint().setFakeBoldText(isSelect); 444 | } 445 | } 446 | } 447 | } 448 | 449 | private float margin; 450 | 451 | private void calcIndicatorRect() { 452 | View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); 453 | float left = currentTabView.getLeft(); 454 | float right = currentTabView.getRight(); 455 | 456 | //for mIndicatorWidthEqualTitle 457 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) { 458 | TextView tab_title = (TextView) currentTabView.findViewById(R.id.tv_tab_title); 459 | mTextPaint.setTextSize(mTextsize); 460 | float textWidth = mTextPaint.measureText(tab_title.getText().toString()); 461 | margin = (right - left - textWidth) / 2; 462 | } 463 | 464 | if (this.mCurrentTab < mTabCount - 1) { 465 | View nextTabView = mTabsContainer.getChildAt(this.mCurrentTab + 1); 466 | float nextTabLeft = nextTabView.getLeft(); 467 | float nextTabRight = nextTabView.getRight(); 468 | 469 | left = left + mCurrentPositionOffset * (nextTabLeft - left); 470 | right = right + mCurrentPositionOffset * (nextTabRight - right); 471 | 472 | //for mIndicatorWidthEqualTitle 473 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) { 474 | TextView next_tab_title = (TextView) nextTabView.findViewById(R.id.tv_tab_title); 475 | mTextPaint.setTextSize(mTextsize); 476 | float nextTextWidth = mTextPaint.measureText(next_tab_title.getText().toString()); 477 | float nextMargin = (nextTabRight - nextTabLeft - nextTextWidth) / 2; 478 | margin = margin + mCurrentPositionOffset * (nextMargin - margin); 479 | } 480 | } 481 | 482 | mIndicatorRect.left = (int) left; 483 | mIndicatorRect.right = (int) right; 484 | //for mIndicatorWidthEqualTitle 485 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) { 486 | mIndicatorRect.left = (int) (left + margin - 1); 487 | mIndicatorRect.right = (int) (right - margin - 1); 488 | } 489 | 490 | mTabRect.left = (int) left; 491 | mTabRect.right = (int) right; 492 | 493 | if (mIndicatorWidth < 0) { //indicatorWidth小于0时,原jpardogo's PagerSlidingTabStrip 494 | 495 | } else {//indicatorWidth大于0时,圆角矩形以及三角形 496 | float indicatorLeft = currentTabView.getLeft() + (currentTabView.getWidth() - mIndicatorWidth) / 2; 497 | 498 | if (this.mCurrentTab < mTabCount - 1) { 499 | View nextTab = mTabsContainer.getChildAt(this.mCurrentTab + 1); 500 | indicatorLeft = indicatorLeft + mCurrentPositionOffset * (currentTabView.getWidth() / 2 + nextTab.getWidth() / 2); 501 | } 502 | 503 | mIndicatorRect.left = (int) indicatorLeft; 504 | mIndicatorRect.right = (int) (mIndicatorRect.left + mIndicatorWidth); 505 | } 506 | } 507 | 508 | @Override 509 | protected void onDraw(Canvas canvas) { 510 | super.onDraw(canvas); 511 | 512 | if (isInEditMode() || mTabCount <= 0) { 513 | return; 514 | } 515 | 516 | int height = getHeight(); 517 | int paddingLeft = getPaddingLeft(); 518 | // draw divider 519 | if (mDividerWidth > 0) { 520 | mDividerPaint.setStrokeWidth(mDividerWidth); 521 | mDividerPaint.setColor(mDividerColor); 522 | for (int i = 0; i < mTabCount - 1; i++) { 523 | View tab = mTabsContainer.getChildAt(i); 524 | canvas.drawLine(paddingLeft + tab.getRight(), mDividerPadding, paddingLeft + tab.getRight(), height - mDividerPadding, mDividerPaint); 525 | } 526 | } 527 | 528 | // draw underline 529 | if (mUnderlineHeight > 0) { 530 | mRectPaint.setColor(mUnderlineColor); 531 | if (mUnderlineGravity == Gravity.BOTTOM) { 532 | canvas.drawRect(paddingLeft, height - mUnderlineHeight, mTabsContainer.getWidth() + paddingLeft, height, mRectPaint); 533 | } else { 534 | canvas.drawRect(paddingLeft, 0, mTabsContainer.getWidth() + paddingLeft, mUnderlineHeight, mRectPaint); 535 | } 536 | } 537 | 538 | //draw indicator line 539 | 540 | calcIndicatorRect(); 541 | if (mIndicatorStyle == STYLE_TRIANGLE) { 542 | if (mIndicatorHeight > 0) { 543 | mTrianglePaint.setColor(mIndicatorColor); 544 | mTrianglePath.reset(); 545 | mTrianglePath.moveTo(paddingLeft + mIndicatorRect.left, height); 546 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.left / 2 + mIndicatorRect.right / 2, height - mIndicatorHeight); 547 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.right, height); 548 | mTrianglePath.close(); 549 | canvas.drawPath(mTrianglePath, mTrianglePaint); 550 | } 551 | } else if (mIndicatorStyle == STYLE_BLOCK) { 552 | if (mIndicatorHeight < 0) { 553 | mIndicatorHeight = height - mIndicatorMarginTop - mIndicatorMarginBottom; 554 | } else { 555 | 556 | } 557 | 558 | if (mIndicatorHeight > 0) { 559 | if (mIndicatorCornerRadius < 0 || mIndicatorCornerRadius > mIndicatorHeight / 2) { 560 | mIndicatorCornerRadius = mIndicatorHeight / 2; 561 | } 562 | 563 | mIndicatorDrawable.setColor(mIndicatorColor); 564 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 565 | (int) mIndicatorMarginTop, (int) (paddingLeft + mIndicatorRect.right - mIndicatorMarginRight), 566 | (int) (mIndicatorMarginTop + mIndicatorHeight)); 567 | mIndicatorDrawable.setCornerRadius(mIndicatorCornerRadius); 568 | mIndicatorDrawable.draw(canvas); 569 | } 570 | } else { 571 | /* mRectPaint.setColor(mIndicatorColor); 572 | calcIndicatorRect(); 573 | canvas.drawRect(getPaddingLeft() + mIndicatorRect.left, getScreenHeight() - mIndicatorHeight, 574 | mIndicatorRect.right + getPaddingLeft(), getScreenHeight(), mRectPaint);*/ 575 | 576 | if (mIndicatorHeight > 0) { 577 | mIndicatorDrawable.setColor(mIndicatorColor); 578 | 579 | if (mIndicatorGravity == Gravity.BOTTOM) { 580 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 581 | height - (int) mIndicatorHeight - (int) mIndicatorMarginBottom, 582 | paddingLeft + mIndicatorRect.right - (int) mIndicatorMarginRight, 583 | height - (int) mIndicatorMarginBottom); 584 | } else { 585 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 586 | (int) mIndicatorMarginTop, 587 | paddingLeft + mIndicatorRect.right - (int) mIndicatorMarginRight, 588 | (int) mIndicatorHeight + (int) mIndicatorMarginTop); 589 | } 590 | mIndicatorDrawable.setCornerRadius(mIndicatorCornerRadius); 591 | mIndicatorDrawable.draw(canvas); 592 | } 593 | } 594 | } 595 | 596 | //setter and getter 597 | public void setCurrentTab(int currentTab) { 598 | this.mCurrentTab = currentTab; 599 | mViewPager.setCurrentItem(currentTab); 600 | 601 | } 602 | 603 | public void setCurrentTab(int currentTab, boolean smoothScroll) { 604 | this.mCurrentTab = currentTab; 605 | mViewPager.setCurrentItem(currentTab, smoothScroll); 606 | } 607 | 608 | public void setIndicatorStyle(int indicatorStyle) { 609 | this.mIndicatorStyle = indicatorStyle; 610 | invalidate(); 611 | } 612 | 613 | public void setTabPadding(float tabPadding) { 614 | this.mTabPadding = dp2px(tabPadding); 615 | updateTabStyles(); 616 | } 617 | 618 | public void setTabSpaceEqual(boolean tabSpaceEqual) { 619 | this.mTabSpaceEqual = tabSpaceEqual; 620 | updateTabStyles(); 621 | } 622 | 623 | public void setTabWidth(float tabWidth) { 624 | this.mTabWidth = dp2px(tabWidth); 625 | updateTabStyles(); 626 | } 627 | 628 | public void setIndicatorColor(int indicatorColor) { 629 | this.mIndicatorColor = indicatorColor; 630 | invalidate(); 631 | } 632 | 633 | public void setIndicatorHeight(float indicatorHeight) { 634 | this.mIndicatorHeight = dp2px(indicatorHeight); 635 | invalidate(); 636 | } 637 | 638 | public void setIndicatorWidth(float indicatorWidth) { 639 | this.mIndicatorWidth = dp2px(indicatorWidth); 640 | invalidate(); 641 | } 642 | 643 | public void setIndicatorCornerRadius(float indicatorCornerRadius) { 644 | this.mIndicatorCornerRadius = dp2px(indicatorCornerRadius); 645 | invalidate(); 646 | } 647 | 648 | public void setIndicatorGravity(int indicatorGravity) { 649 | this.mIndicatorGravity = indicatorGravity; 650 | invalidate(); 651 | } 652 | 653 | public void setIndicatorMargin(float indicatorMarginLeft, float indicatorMarginTop, 654 | float indicatorMarginRight, float indicatorMarginBottom) { 655 | this.mIndicatorMarginLeft = dp2px(indicatorMarginLeft); 656 | this.mIndicatorMarginTop = dp2px(indicatorMarginTop); 657 | this.mIndicatorMarginRight = dp2px(indicatorMarginRight); 658 | this.mIndicatorMarginBottom = dp2px(indicatorMarginBottom); 659 | invalidate(); 660 | } 661 | 662 | public void setIndicatorWidthEqualTitle(boolean indicatorWidthEqualTitle) { 663 | this.mIndicatorWidthEqualTitle = indicatorWidthEqualTitle; 664 | invalidate(); 665 | } 666 | 667 | public void setUnderlineColor(int underlineColor) { 668 | this.mUnderlineColor = underlineColor; 669 | invalidate(); 670 | } 671 | 672 | public void setUnderlineHeight(float underlineHeight) { 673 | this.mUnderlineHeight = dp2px(underlineHeight); 674 | invalidate(); 675 | } 676 | 677 | public void setUnderlineGravity(int underlineGravity) { 678 | this.mUnderlineGravity = underlineGravity; 679 | invalidate(); 680 | } 681 | 682 | public void setDividerColor(int dividerColor) { 683 | this.mDividerColor = dividerColor; 684 | invalidate(); 685 | } 686 | 687 | public void setDividerWidth(float dividerWidth) { 688 | this.mDividerWidth = dp2px(dividerWidth); 689 | invalidate(); 690 | } 691 | 692 | public void setDividerPadding(float dividerPadding) { 693 | this.mDividerPadding = dp2px(dividerPadding); 694 | invalidate(); 695 | } 696 | 697 | public void setTextsize(float textsize) { 698 | this.mTextsize = sp2px(textsize); 699 | updateTabStyles(); 700 | } 701 | 702 | public void setTextSelectColor(int textSelectColor) { 703 | this.mTextSelectColor = textSelectColor; 704 | updateTabStyles(); 705 | } 706 | 707 | public void setTextUnselectColor(int textUnselectColor) { 708 | this.mTextUnselectColor = textUnselectColor; 709 | updateTabStyles(); 710 | } 711 | 712 | public void setTextBold(int textBold) { 713 | this.mTextBold = textBold; 714 | updateTabStyles(); 715 | } 716 | 717 | public void setTextAllCaps(boolean textAllCaps) { 718 | this.mTextAllCaps = textAllCaps; 719 | updateTabStyles(); 720 | } 721 | 722 | public void setSnapOnTabClick(boolean snapOnTabClick) { 723 | mSnapOnTabClick = snapOnTabClick; 724 | } 725 | 726 | 727 | public int getTabCount() { 728 | return mTabCount; 729 | } 730 | 731 | public int getCurrentTab() { 732 | return mCurrentTab; 733 | } 734 | 735 | public int getIndicatorStyle() { 736 | return mIndicatorStyle; 737 | } 738 | 739 | public float getTabPadding() { 740 | return mTabPadding; 741 | } 742 | 743 | public boolean isTabSpaceEqual() { 744 | return mTabSpaceEqual; 745 | } 746 | 747 | public float getTabWidth() { 748 | return mTabWidth; 749 | } 750 | 751 | public int getIndicatorColor() { 752 | return mIndicatorColor; 753 | } 754 | 755 | public float getIndicatorHeight() { 756 | return mIndicatorHeight; 757 | } 758 | 759 | public float getIndicatorWidth() { 760 | return mIndicatorWidth; 761 | } 762 | 763 | public float getIndicatorCornerRadius() { 764 | return mIndicatorCornerRadius; 765 | } 766 | 767 | public float getIndicatorMarginLeft() { 768 | return mIndicatorMarginLeft; 769 | } 770 | 771 | public float getIndicatorMarginTop() { 772 | return mIndicatorMarginTop; 773 | } 774 | 775 | public float getIndicatorMarginRight() { 776 | return mIndicatorMarginRight; 777 | } 778 | 779 | public float getIndicatorMarginBottom() { 780 | return mIndicatorMarginBottom; 781 | } 782 | 783 | public int getUnderlineColor() { 784 | return mUnderlineColor; 785 | } 786 | 787 | public float getUnderlineHeight() { 788 | return mUnderlineHeight; 789 | } 790 | 791 | public int getDividerColor() { 792 | return mDividerColor; 793 | } 794 | 795 | public float getDividerWidth() { 796 | return mDividerWidth; 797 | } 798 | 799 | public float getDividerPadding() { 800 | return mDividerPadding; 801 | } 802 | 803 | public float getTextsize() { 804 | return mTextsize; 805 | } 806 | 807 | public int getTextSelectColor() { 808 | return mTextSelectColor; 809 | } 810 | 811 | public int getTextUnselectColor() { 812 | return mTextUnselectColor; 813 | } 814 | 815 | public int getTextBold() { 816 | return mTextBold; 817 | } 818 | 819 | public boolean isTextAllCaps() { 820 | return mTextAllCaps; 821 | } 822 | 823 | public TextView getTitleView(int tab) { 824 | View tabView = mTabsContainer.getChildAt(tab); 825 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 826 | return tv_tab_title; 827 | } 828 | 829 | //setter and getter 830 | 831 | // show MsgTipView 832 | private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 833 | private SparseArray mInitSetMap = new SparseArray<>(); 834 | 835 | private OnTabSelectListener mListener; 836 | 837 | public interface OnTabSelectListener { 838 | void onTabSelect(int position); 839 | 840 | void onTabReselect(int position); 841 | } 842 | 843 | public void setOnTabSelectListener(OnTabSelectListener listener) { 844 | this.mListener = listener; 845 | } 846 | 847 | class InnerPagerAdapter extends FragmentPagerAdapter { 848 | private ArrayList fragments = new ArrayList<>(); 849 | private String[] titles; 850 | 851 | public InnerPagerAdapter(FragmentManager fm, ArrayList fragments, String[] titles) { 852 | super(fm); 853 | this.fragments = fragments; 854 | this.titles = titles; 855 | } 856 | 857 | @Override 858 | public int getCount() { 859 | return fragments.size(); 860 | } 861 | 862 | @Override 863 | public CharSequence getPageTitle(int position) { 864 | return titles[position]; 865 | } 866 | 867 | @Override 868 | public Fragment getItem(int position) { 869 | return fragments.get(position); 870 | } 871 | 872 | @Override 873 | public void destroyItem(ViewGroup container, int position, Object object) { 874 | // 覆写destroyItem并且空实现,这样每个Fragment中的视图就不会被销毁 875 | // super.destroyItem(container, position, object); 876 | } 877 | 878 | @Override 879 | public int getItemPosition(Object object) { 880 | return PagerAdapter.POSITION_NONE; 881 | } 882 | } 883 | 884 | @Override 885 | protected Parcelable onSaveInstanceState() { 886 | Bundle bundle = new Bundle(); 887 | bundle.putParcelable("instanceState", super.onSaveInstanceState()); 888 | bundle.putInt("mCurrentTab", mCurrentTab); 889 | return bundle; 890 | } 891 | 892 | @Override 893 | protected void onRestoreInstanceState(Parcelable state) { 894 | if (state instanceof Bundle) { 895 | Bundle bundle = (Bundle) state; 896 | mCurrentTab = bundle.getInt("mCurrentTab"); 897 | state = bundle.getParcelable("instanceState"); 898 | if (mCurrentTab != 0 && mTabsContainer.getChildCount() > 0) { 899 | updateTabSelection(mCurrentTab); 900 | scrollToCurrentTab(); 901 | } 902 | } 903 | super.onRestoreInstanceState(state); 904 | } 905 | 906 | protected int dp2px(float dp) { 907 | final float scale = mContext.getResources().getDisplayMetrics().density; 908 | return (int) (dp * scale + 0.5f); 909 | } 910 | 911 | protected int sp2px(float sp) { 912 | final float scale = this.mContext.getResources().getDisplayMetrics().scaledDensity; 913 | return (int) (sp * scale + 0.5f); 914 | } 915 | } --------------------------------------------------------------------------------