├── .github
└── workflows
│ └── android.yml
├── .gitignore
├── .idea
├── .gitignore
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── deploymentTargetDropDown.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── ktlint.xml
├── misc.xml
├── sonarlint
│ └── issuestore
│ │ ├── 0
│ │ ├── 0
│ │ │ └── 00bb5a55bbe67254c2c0f18c038e34feb530225d
│ │ └── 5
│ │ │ └── 05efc8b1657769a27696d478ded1e95f38737233
│ │ ├── 1
│ │ └── f
│ │ │ └── 1ff699be707da39c21730447e3621d6e9c1c97a0
│ │ ├── 2
│ │ └── 3
│ │ │ └── 23c044a63385dd34c0d6c43e119c336cfa71e078
│ │ ├── 3
│ │ ├── 7
│ │ │ └── 37e0b1be3ca00e70c871059689243c15c34fb44a
│ │ ├── b
│ │ │ └── 3b68b8031302659b137c3fe69d6becbaf2b9698b
│ │ ├── e
│ │ │ └── 3eb353299266c592fdca2efa765a13c86f79c544
│ │ └── f
│ │ │ ├── 3f3614b660c98ad2ec2ec3066dba56ad2e3556ca
│ │ │ └── 3fc4d5b5fd8ab8664c5f8c8311e06787ab8690b3
│ │ ├── 4
│ │ ├── 0
│ │ │ └── 40a8402d1b0b07de9d145da63f2160b6295c3891
│ │ ├── 3
│ │ │ └── 43ff339470fe0f3ddf25ea6f557a45637fdd013a
│ │ └── 7
│ │ │ └── 47bbea10edd9b7e1f95973e3bc34e51228d8be69
│ │ ├── 5
│ │ ├── 1
│ │ │ └── 51e1c5d383dfaa35e0e7e5873a0a99355a86880f
│ │ ├── 2
│ │ │ └── 5282ed8d7d3284c85a70c413ef67c04b412e32f6
│ │ ├── 6
│ │ │ └── 566d6c7352aef9d8aa71dde9d56660f1ef22e62a
│ │ └── 8
│ │ │ └── 5804b5b5d9744a69cf54dab9c9f4c6ad2ddc7f9a
│ │ ├── 6
│ │ ├── 1
│ │ │ └── 61d258e93d3569d213e6b23af507edcc557e5e07
│ │ ├── 2
│ │ │ └── 623b11249d525083e5fe898c5cd1762660f51ac9
│ │ ├── 6
│ │ │ ├── 66735bbb2138471309e49d49ac3a370569cc125d
│ │ │ └── 66e2568e0d3f2652c945513ba940f8f1e4f6813b
│ │ ├── 8
│ │ │ └── 68ffd980bc165482d8ae7b3a067170a25a807e2e
│ │ └── e
│ │ │ └── 6ecd6000a7b6f4a2884412ff19f74193ed089648
│ │ ├── 7
│ │ └── 3
│ │ │ └── 73fe5d7605274dc2cf5a3a110a86d3ec5c0bd0f3
│ │ ├── 8
│ │ └── e
│ │ │ └── 8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d
│ │ ├── 9
│ │ └── e
│ │ │ └── 9e08934d811afe28fbc77aaa3c0d747b94348db9
│ │ ├── a
│ │ ├── 5
│ │ │ └── a52f34ee8999426f9468ca72a1b11663f57ba0c4
│ │ ├── d
│ │ │ └── ad4f6a39172f1fe4db70a527df96a9649f3d3a51
│ │ └── f
│ │ │ └── af824a66dbada2cce18cd1f25c80c537f9fad500
│ │ ├── b
│ │ └── 4
│ │ │ └── b4a4dd424d0575baea7f6bd8cfcb4f9e71b79bc8
│ │ ├── c
│ │ └── 3
│ │ │ └── c3085a1c4f578eadc45c5499e731342f9401e3bc
│ │ ├── e
│ │ ├── a
│ │ │ └── eaf9a1dbcdebe8d5b11f84b26a2c1d99b9a4239d
│ │ └── c
│ │ │ └── ecbd5dff7dda95f168e5b6d66ad952c7b937c260
│ │ ├── f
│ │ ├── 0
│ │ │ ├── f07866736216be0ee2aba49e392191aeae700a35
│ │ │ └── f0a9ce0e88c5e949614ef2d596a07dc5b52bf1ee
│ │ └── c
│ │ │ └── fcead86d2672c263fadb598b07bf805b5822dec7
│ │ └── index.pb
└── vcs.xml
├── README.md
├── apk
└── xiaomi
│ └── 345
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── lvkang
│ │ └── example
│ │ └── ExampleInstrumentedTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ ├── skin.skin
│ ├── skin1.skin
│ ├── skin2.skin
│ ├── skin3.skin
│ ├── skin4.zip
│ ├── skin5.skin
│ ├── skin6.skin
│ └── skin7.skin
│ ├── java
│ └── com
│ │ └── lvkang
│ │ └── example
│ │ ├── BaseApplication.kt
│ │ ├── MainActivity.kt
│ │ ├── Test.kt
│ │ ├── TestActivity.kt
│ │ └── TestAdapter.kt
│ └── res
│ ├── drawable
│ ├── skin_edit_background.xml
│ ├── skin_edit_cursor.xml
│ ├── skin_main_background.xml
│ └── skin_main_image.jpg
│ ├── layout
│ ├── activity_main.xml
│ ├── activity_test.xml
│ ├── layout_include.xml
│ └── test_item.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
│ ├── values-night
│ └── themes.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── themes.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── skin
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── libs
└── zip4j-2.9.0.jar
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── com
│ └── lvkang
│ └── skin
│ └── ExampleInstrumentedTest.kt
├── main
├── AndroidManifest.xml
├── java
│ └── com
│ │ └── lvkang
│ │ └── skin
│ │ ├── SkinManager.kt
│ │ ├── app
│ │ ├── SkinActivityLifecycle.kt
│ │ └── SkinCompatActivity.kt
│ │ ├── config
│ │ ├── SkinKey.kt
│ │ └── SkinPreUtils.kt
│ │ ├── factory
│ │ └── SkinCompateFactory.kt
│ │ ├── inflater
│ │ ├── SkinAppCompatViewInflater.kt
│ │ ├── SkinLayoutInflater.kt
│ │ └── SkinViewInflater.kt
│ │ ├── ktx
│ │ └── SkinHelper.kt
│ │ ├── listener
│ │ └── SkinLoadListener.kt
│ │ ├── obsreve
│ │ ├── SkinObserver.kt
│ │ └── SkinObserverable.kt
│ │ ├── resource
│ │ ├── AbstractSkinLoadStrategy.kt
│ │ ├── SkinCompatResources.kt
│ │ ├── SkinLoadStrategyEnum.kt
│ │ └── strategy
│ │ │ ├── AbstractSkinLoadAssetsImpl.kt
│ │ │ ├── AbstractSkinLoadNoneImpl.kt
│ │ │ ├── AbstractSkinLoadStorageImpl.kt
│ │ │ └── AbstractSkinLoadZipImpl.kt
│ │ ├── util
│ │ └── SkinLog.kt
│ │ └── wedget
│ │ ├── SkinCompatHelper.kt
│ │ ├── SkinCompatSupportable.kt
│ │ ├── android
│ │ ├── FrameLayoutX.kt
│ │ ├── RelativeLayoutX.kt
│ │ └── ViewX.kt
│ │ ├── androidx
│ │ ├── ButtonX.kt
│ │ ├── ConstraintLayoutX.kt
│ │ ├── EditTextX.kt
│ │ ├── ImageViewX.kt
│ │ ├── LinearLayoutX.kt
│ │ ├── NestedScrollViewX.kt
│ │ ├── TextViewX.kt
│ │ └── cardview
│ │ │ ├── CardViewX.kt
│ │ │ └── RoundRectDrawable.kt
│ │ └── helper
│ │ ├── SkinCompatBackgroundHelper.kt
│ │ ├── SkinCompatCardHelper.kt
│ │ ├── SkinCompatEditTextHelpter.kt
│ │ ├── SkinCompatImageHelper.kt
│ │ └── SkinCompatTextHelper.kt
└── res
│ └── values
│ └── attrs.xml
└── test
└── java
└── com
└── lvkang
└── skin
└── ExampleUnitTest.kt
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: set up JDK 11
17 | uses: actions/setup-java@v2
18 | with:
19 | java-version: '11'
20 | distribution: 'adopt'
21 | cache: gradle
22 |
23 | - name: Grant execute permission for gradlew
24 | run: chmod +x gradlew
25 | - name: Build with Gradle
26 | run: ./gradlew assembleRelease
27 |
28 | - name: Upload a Build Artifact
29 | uses: actions/upload-artifact@v2.3.1
30 | with:
31 | # Artifact name
32 | name: upload-apk
33 | # A file, directory or wildcard pattern that describes what to upload
34 | path: |
35 | ${{github.workspace}}/apk/*.apk
36 | ${{github.workspace}}/apk/xiaomi/*.apk
37 |
--------------------------------------------------------------------------------
/.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 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.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 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.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 |
--------------------------------------------------------------------------------
/.idea/ktlint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/0/0/00bb5a55bbe67254c2c0f18c038e34feb530225d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/0/0/00bb5a55bbe67254c2c0f18c038e34feb530225d
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/0/5/05efc8b1657769a27696d478ded1e95f38737233:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/0/5/05efc8b1657769a27696d478ded1e95f38737233
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/1/f/1ff699be707da39c21730447e3621d6e9c1c97a0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/1/f/1ff699be707da39c21730447e3621d6e9c1c97a0
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/2/3/23c044a63385dd34c0d6c43e119c336cfa71e078:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/2/3/23c044a63385dd34c0d6c43e119c336cfa71e078
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/3/7/37e0b1be3ca00e70c871059689243c15c34fb44a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/3/7/37e0b1be3ca00e70c871059689243c15c34fb44a
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/3/b/3b68b8031302659b137c3fe69d6becbaf2b9698b:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/3/b/3b68b8031302659b137c3fe69d6becbaf2b9698b
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/3/e/3eb353299266c592fdca2efa765a13c86f79c544:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/3/e/3eb353299266c592fdca2efa765a13c86f79c544
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/3/f/3f3614b660c98ad2ec2ec3066dba56ad2e3556ca:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/3/f/3f3614b660c98ad2ec2ec3066dba56ad2e3556ca
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/3/f/3fc4d5b5fd8ab8664c5f8c8311e06787ab8690b3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/3/f/3fc4d5b5fd8ab8664c5f8c8311e06787ab8690b3
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/4/0/40a8402d1b0b07de9d145da63f2160b6295c3891:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/4/0/40a8402d1b0b07de9d145da63f2160b6295c3891
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/4/3/43ff339470fe0f3ddf25ea6f557a45637fdd013a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/4/3/43ff339470fe0f3ddf25ea6f557a45637fdd013a
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/4/7/47bbea10edd9b7e1f95973e3bc34e51228d8be69:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/4/7/47bbea10edd9b7e1f95973e3bc34e51228d8be69
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/5/1/51e1c5d383dfaa35e0e7e5873a0a99355a86880f:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/5/1/51e1c5d383dfaa35e0e7e5873a0a99355a86880f
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/5/2/5282ed8d7d3284c85a70c413ef67c04b412e32f6:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/5/2/5282ed8d7d3284c85a70c413ef67c04b412e32f6
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/5/6/566d6c7352aef9d8aa71dde9d56660f1ef22e62a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/5/6/566d6c7352aef9d8aa71dde9d56660f1ef22e62a
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/5/8/5804b5b5d9744a69cf54dab9c9f4c6ad2ddc7f9a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/5/8/5804b5b5d9744a69cf54dab9c9f4c6ad2ddc7f9a
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/6/1/61d258e93d3569d213e6b23af507edcc557e5e07:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/6/1/61d258e93d3569d213e6b23af507edcc557e5e07
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/6/2/623b11249d525083e5fe898c5cd1762660f51ac9:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/6/2/623b11249d525083e5fe898c5cd1762660f51ac9
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/6/6/66735bbb2138471309e49d49ac3a370569cc125d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/6/6/66735bbb2138471309e49d49ac3a370569cc125d
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/6/6/66e2568e0d3f2652c945513ba940f8f1e4f6813b:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/6/6/66e2568e0d3f2652c945513ba940f8f1e4f6813b
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/6/8/68ffd980bc165482d8ae7b3a067170a25a807e2e:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/6/8/68ffd980bc165482d8ae7b3a067170a25a807e2e
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/6/e/6ecd6000a7b6f4a2884412ff19f74193ed089648:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/6/e/6ecd6000a7b6f4a2884412ff19f74193ed089648
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/7/3/73fe5d7605274dc2cf5a3a110a86d3ec5c0bd0f3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/7/3/73fe5d7605274dc2cf5a3a110a86d3ec5c0bd0f3
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/9/e/9e08934d811afe28fbc77aaa3c0d747b94348db9:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/9/e/9e08934d811afe28fbc77aaa3c0d747b94348db9
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/a/5/a52f34ee8999426f9468ca72a1b11663f57ba0c4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/a/5/a52f34ee8999426f9468ca72a1b11663f57ba0c4
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/a/d/ad4f6a39172f1fe4db70a527df96a9649f3d3a51:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/a/d/ad4f6a39172f1fe4db70a527df96a9649f3d3a51
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/a/f/af824a66dbada2cce18cd1f25c80c537f9fad500:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/a/f/af824a66dbada2cce18cd1f25c80c537f9fad500
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/b/4/b4a4dd424d0575baea7f6bd8cfcb4f9e71b79bc8:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/b/4/b4a4dd424d0575baea7f6bd8cfcb4f9e71b79bc8
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/c/3/c3085a1c4f578eadc45c5499e731342f9401e3bc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/c/3/c3085a1c4f578eadc45c5499e731342f9401e3bc
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/e/a/eaf9a1dbcdebe8d5b11f84b26a2c1d99b9a4239d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/e/a/eaf9a1dbcdebe8d5b11f84b26a2c1d99b9a4239d
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/e/c/ecbd5dff7dda95f168e5b6d66ad952c7b937c260:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/e/c/ecbd5dff7dda95f168e5b6d66ad952c7b937c260
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/f/0/f07866736216be0ee2aba49e392191aeae700a35:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/f/0/f07866736216be0ee2aba49e392191aeae700a35
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/f/0/f0a9ce0e88c5e949614ef2d596a07dc5b52bf1ee:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/f/0/f0a9ce0e88c5e949614ef2d596a07dc5b52bf1ee
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/f/c/fcead86d2672c263fadb598b07bf805b5822dec7:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/f/c/fcead86d2672c263fadb598b07bf805b5822dec7
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/index.pb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/.idea/sonarlint/issuestore/index.pb
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AndroidSkin
2 | ### 一个使用成本极低的换肤框架
3 |
4 | - 支持插件式换肤
5 | - 支持继承和非继承式换肤,无需对base层进行代码侵入
6 | - 支持自定义View
7 | - 支持多种加载类型,Assets,Storage ,Zip 等
8 | - 内部直接进行换肤,无需重启
9 |
10 | ### 目前支持的内置 View 👨🔧
11 |
12 | ___
13 |
14 |
15 |
16 | | | backaground | Des |
17 | | ------------------ | :----------------------------: | :--: |
18 | | AppCompatTextView | 背景,字体颜色,字体大小 | |
19 | | AppCompatButton | 背景,字体颜色,字体大小 | |
20 | | AppCompatImageView | 背景,image | |
21 | | ConstraintLayout | 背景 | |
22 | | LinearLayoutCompat | 背景 | |
23 | | NestedScrollView | 背景 | |
24 | | FrameLayout | 背景 | |
25 | | RelativeLayout | 背景 | |
26 | | AppCompatEditText | 背景,字体颜色,字体大小,hint | |
27 | | CardView | 背景,圆角,elevation | |
28 |
29 | > 其他的陆续支持中。
30 | > 你也可以克隆代码到本地进行适配然后再提交代码,期待你的提交
31 |
32 | ### 使用方式
33 |
34 | #### 初始化
35 |
36 | ```
37 | SkinManager.init(this)
38 | .addInflaters(SkinAppCompatViewInflater())
39 | .setAutoLoadSkin(true)
40 | .build()
41 | ```
42 |
43 | - SkinAppCompatViewInflater
44 |
45 | 框架内部支持换肤的 View , 如果需要对自定义 View 进行换肤,可实现 SkinLayoutInflater 接口,具体可参考 `SkinAppCompatViewInflater` 内部实现。
46 |
47 | 最后将自定义 Inflater 在初始化时添加即可
48 |
49 | - setAutoLoadSkin
50 |
51 | 是否使用非继承式的方式实现换肤,默认 true
52 |
53 | #### 加载皮肤
54 |
55 | - None
56 |
57 | 加载默认皮肤,即不使用皮肤
58 |
59 | - Assets
60 |
61 | 将皮肤文件放在 assets 目录下,传入即可
62 |
63 | ```kotlin
64 | /**
65 | * 加载资源文件夹下的皮肤
66 | * @param name 资源文件名
67 | * @param skinLoadListener 回调
68 | * @param isRepeat false 表示要加载的 skin 和当前使用的相同时不重复加载
69 | */
70 | fun loadAssetsSkin(
71 | name: String,
72 | skinLoadListener: SkinLoadListener? = null,
73 | isRepeat: Boolean = false,
74 | )
75 | ```
76 |
77 | - Storage
78 |
79 | 将皮肤文件下载到沙箱中,传入绝对路径即可
80 |
81 | ```kotlin
82 | /**
83 | * 加载内部存储下的皮肤,必须是沙箱路径
84 | * @param path skin 绝对路径
85 | * @param skinLoadListener 回调
86 | * @param isRepeat false 表示要加载的 skin 和当前使用的相同时不重复加载
87 | */
88 | fun loadStorageSkin(
89 | path: String,
90 | skinLoadListener: SkinLoadListener? = null,
91 | isRepeat: Boolean = false
92 | )
93 | ```
94 |
95 | - Zip
96 |
97 | 将下载好的 zip 的绝对路径传入即可,如果是加密的Zip,则需要传入密码
98 |
99 | ```kotlin
100 | /**
101 | * 加载内部存储下的 zpi 皮肤文件,必须是沙箱路径
102 | * @param path skin 绝对路径
103 | * @param password zip 文件密码,没有可不传
104 | * @param skinLoadListener 回调
105 | * @param isRepeat false 表示要加载的 skin 和当前使用的相同时不重复加载
106 | */
107 | fun loadZipSkin(
108 | path: String,
109 | password: String? = null,
110 | skinLoadListener: SkinLoadListener? = null,
111 | isRepeat: Boolean = false
112 | )
113 | ```
114 |
115 |
116 |
117 | ### 需要注意的点
118 |
119 | - 创建皮肤
120 |
121 | 新建一个项目,将需要换肤的资源放在此项目中,然后修改资源值即可。
122 |
123 | > 注意,资源名称必须和主项目资源名称一致,后缀必须是 .skin 。压缩文件的原文件也是如此
124 |
125 | - 更新皮肤资源
126 |
127 | 如果当前使用的皮肤 `red.skin` 内部的资源发生了改变,则需要修改皮肤名称,例如修改为 `red_1.skin` 。这样做的原因是本框架中对于从 `Assets` 以及 `Zip` 加载类型的有缓存策略,如果不修改名称,默认就会加载到缓存皮肤中。
128 |
129 |
--------------------------------------------------------------------------------
/apk/xiaomi/345:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/apk/xiaomi/345
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdkVersion 30
8 | buildToolsVersion "30.0.2"
9 |
10 | defaultConfig {
11 | applicationId "com.lvkang.example"
12 | minSdkVersion 21
13 | targetSdkVersion 30
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
37 | implementation 'androidx.core:core-ktx:1.6.0'
38 | implementation 'androidx.appcompat:appcompat:1.3.1'
39 | implementation 'com.google.android.material:material:1.4.0'
40 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
41 | implementation project(path: ':skin')
42 | testImplementation 'junit:junit:4.13.2'
43 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
45 | }
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/lvkang/example/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.example
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.lvkang.androidskin", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/assets/skin.skin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/assets/skin.skin
--------------------------------------------------------------------------------
/app/src/main/assets/skin1.skin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/assets/skin1.skin
--------------------------------------------------------------------------------
/app/src/main/assets/skin2.skin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/assets/skin2.skin
--------------------------------------------------------------------------------
/app/src/main/assets/skin3.skin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/assets/skin3.skin
--------------------------------------------------------------------------------
/app/src/main/assets/skin4.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/assets/skin4.zip
--------------------------------------------------------------------------------
/app/src/main/assets/skin5.skin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/assets/skin5.skin
--------------------------------------------------------------------------------
/app/src/main/assets/skin6.skin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/assets/skin6.skin
--------------------------------------------------------------------------------
/app/src/main/assets/skin7.skin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/assets/skin7.skin
--------------------------------------------------------------------------------
/app/src/main/java/com/lvkang/example/BaseApplication.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.example
2 |
3 | import android.app.Application
4 | import com.lvkang.skin.SkinManager
5 | import com.lvkang.skin.inflater.SkinAppCompatViewInflater
6 |
7 | /**
8 | * @name BaseActivity
9 | * @package com.lvkang.example
10 | * @author 345 QQ:1831712732
11 | * @time 2020/12/02 22:13
12 | * @description
13 | */
14 | class BaseApplication : Application() {
15 |
16 | override fun onCreate() {
17 | super.onCreate()
18 | SkinManager.init(this)
19 | .addInflaters(SkinAppCompatViewInflater())
20 | .setAutoLoadSkin(true)
21 | .build()
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lvkang/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.example
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.view.View
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.core.content.res.ResourcesCompat
8 | import com.lvkang.skin.SkinManager
9 | import com.lvkang.skin.ktx.isFile
10 | import java.io.File
11 | import java.io.IOException
12 |
13 | class MainActivity : AppCompatActivity() {
14 |
15 | private val TAG = "MainActivity"
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | setContentView(R.layout.activity_main)
20 |
21 | val view = findViewById(R.id.button)
22 | view.setOnClickListener {
23 | SkinManager.loadAssetsSkin("skin.skin")
24 | }
25 | findViewById(R.id.button1).setOnClickListener {
26 | SkinManager.loadAssetsSkin("skin1.skin")
27 | }
28 |
29 | findViewById(R.id.button2).setOnClickListener {
30 | SkinManager.loadAssetsSkin("skin2.skin")
31 | }
32 |
33 | findViewById(R.id.button3).setOnClickListener {
34 | SkinManager.loadAssetsSkin("skin3.skin")
35 | }
36 |
37 | findViewById(R.id.button4).setOnClickListener {
38 |
39 | //先将zip复制到沙箱中
40 | val path = copyCache(
41 | "skin4.zip",
42 | "${SkinManager.getApplication().getExternalFilesDir("file")}${File.separator}"
43 | )
44 | path?.run {
45 | SkinManager.loadZipSkin(path, password = "3310")
46 | }
47 | }
48 |
49 | findViewById(R.id.button5).setOnClickListener {
50 | SkinManager.loadAssetsSkin("skin5.skin")
51 | }
52 |
53 | findViewById(R.id.none).setOnClickListener {
54 | SkinManager.loadNone()
55 | }
56 |
57 | findViewById(R.id.next).setOnClickListener {
58 | startActivity(Intent(this, TestActivity::class.java))
59 | // findViewById(R.id.cardview).setBackgroundResource(
60 | // ResourcesCompat.getColor(
61 | // resources,
62 | // R.color.skin_cardview_color,
63 | // null
64 | // )
65 | // )
66 | }
67 | findViewById(R.id.cardview).setOnClickListener {
68 | SkinManager.loadAssetsSkin("skin7.skin")
69 | // SkinManager.loadAssetsSkin("skin8.skin")
70 | }
71 | }
72 |
73 | private fun copyCache(skinName: String, cacheDir: String): String? {
74 | return try {
75 | val outFile = File(cacheDir, skinName)
76 | if (isFile(outFile.path)) {
77 | outFile.delete()
78 | }
79 | val input = SkinManager.getContext().resources.assets.open(skinName)
80 | input.copyTo(outFile.outputStream())
81 | outFile.absolutePath
82 | } catch (e: IOException) {
83 | e.printStackTrace()
84 | null
85 | }
86 | // findViewById(R.id.cardview).setOnClickListener {
87 | // SkinManager.loadAssetsSkin("skin7.skin")
88 | // }
89 | }
90 |
91 |
92 | }
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvkang/example/Test.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.example
2 |
3 | /**
4 | * @name Test
5 | * @package com.lvkang.example
6 | * @author 345 QQ:1831712732
7 | * @time 2020/12/20 00:55
8 | * @description
9 | */
10 |
11 | // 1000,560
12 | fun test(a: Int, b: Int): Int {
13 | var a1 = a
14 | var b1 = b
15 | while (b1 != 0) {
16 | val temp = a1 % b1
17 | a1 = b1
18 | b1 = temp
19 | }
20 | return b1
21 | }
22 |
23 |
24 | fun test2(a: Int, b: Int): Int {
25 | return if (b != 0) {
26 | test2(b, a % b)
27 | } else {
28 | a
29 | }
30 | }
31 |
32 | fun main() {
33 | println(test2(1000, 560))
34 | }
35 |
36 | fun test1(a: Int, b: Int): Int {
37 | var a1 = a
38 | var b1 = b
39 | if (a < b) {
40 | val t = a1
41 | a1 = b1
42 | b1 = t
43 | }
44 | while (b1 != 0) {
45 | val yu = a1 % b1
46 | a1 = b1
47 | b1 = yu
48 | }
49 | return a1
50 | }
51 |
52 | sealed class Test2 {
53 | val test1: Test2? = null
54 | abstract val test2: Test2
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lvkang/example/TestActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.example
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.lvkang.skin.app.SkinCompatActivity
8 |
9 | /**
10 | * @name TextActivity
11 | * @package com.lvkang.example
12 | * @author 345 QQ:1831712732
13 | * @time 2020/12/14 00:12
14 | * @description
15 | */
16 | class TestActivity : SkinCompatActivity() {
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | setContentView(R.layout.activity_test)
20 |
21 | val recycler = findViewById(R.id.recycler)
22 |
23 | recycler.layoutManager = LinearLayoutManager(this)
24 |
25 | val list = arrayListOf()
26 | for (i in 0..1000)
27 | list.add("")
28 | recycler.adapter = TestAdapter(list)
29 |
30 |
31 | findViewById(R.id.test).setOnClickListener {
32 | recycler?.adapter?.notifyDataSetChanged()
33 | print()
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lvkang/example/TestAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.example
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.appcompat.widget.AppCompatTextView
7 | import androidx.recyclerview.widget.RecyclerView
8 |
9 | /**
10 | * @name TestAdapter
11 | * @package com.lvkang.example
12 | * @author 345 QQ:1831712732
13 | * @time 2022/07/21 17:33
14 | * @description
15 | */
16 | class TestAdapter(val data: List) : RecyclerView.Adapter() {
17 |
18 |
19 | class TestHolder(view: View) : RecyclerView.ViewHolder(view) {
20 | var text = view.findViewById(R.id.text)
21 | }
22 |
23 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestHolder {
24 | var view = LayoutInflater.from(parent.context).inflate(R.layout.test_item, parent, false)
25 | return TestHolder(view)
26 | }
27 |
28 | override fun onBindViewHolder(holder: TestHolder, position: Int) {
29 | holder.text.text = "$position"
30 | }
31 |
32 | override fun getItemCount(): Int {
33 | return data.size
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/skin_edit_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/skin_edit_cursor.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/skin_main_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/skin_main_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/drawable/skin_main_image.jpg
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
17 |
24 |
25 |
26 |
39 |
40 |
53 |
54 |
67 |
68 |
69 |
82 |
83 |
96 |
97 |
107 |
108 |
109 |
121 |
122 |
131 |
132 |
148 |
149 |
161 |
162 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_include.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
20 |
21 |
22 |
23 |
24 |
33 |
34 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/test_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
11 |
12 | #000000
13 | #434aad
14 | #ff0000
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 20sp
4 | 25sp
5 | 10dp
6 | 15dp
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidSkin
3 | 点击换肤
4 | 跳转
5 |
6 | 请输入
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | //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
3 | buildscript {
4 | ext.kotlin_version = "1.5.10"
5 | repositories {
6 | mavenCentral()
7 | google()
8 | }
9 | dependencies {
10 | classpath "com.android.tools.build:gradle:4.1.1"
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | mavenCentral()
21 | google()
22 | maven { url 'https://jitpack.io' }
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
--------------------------------------------------------------------------------
/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=-Xmx1024m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
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 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Dec 02 23:03:43 CST 2020
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-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':skin'
2 | include ':app'
3 |
4 | rootProject.name = "AndroidSkin"
--------------------------------------------------------------------------------
/skin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/skin/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdkVersion 30
8 | buildToolsVersion "30.0.2"
9 |
10 | defaultConfig {
11 | minSdkVersion 21
12 | targetSdkVersion 30
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | consumerProguardFiles "consumer-rules.pro"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation fileTree(dir: "libs", include: ["*.jar"])
37 | //noinspection GradleDependency
38 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
39 | implementation 'androidx.core:core-ktx:1.6.0'
40 | implementation 'androidx.appcompat:appcompat:1.3.1'
41 | implementation 'com.google.android.material:material:1.4.0'
42 | //协程基础库
43 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
44 | //协程 Android 库,提供 UI 调度器
45 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
46 |
47 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
48 |
49 | }
--------------------------------------------------------------------------------
/skin/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/skin/consumer-rules.pro
--------------------------------------------------------------------------------
/skin/libs/zip4j-2.9.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LvKang-insist/AndroidSkin/4eef34397a633fbc412e8bb8353ffd3acf324c56/skin/libs/zip4j-2.9.0.jar
--------------------------------------------------------------------------------
/skin/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
--------------------------------------------------------------------------------
/skin/src/androidTest/java/com/lvkang/skin/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.lvkang.skin.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/skin/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/SkinManager.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import com.lvkang.skin.app.SkinActivityLifecycle
6 | import com.lvkang.skin.config.SkinPreUtils
7 | import com.lvkang.skin.inflater.SkinLayoutInflater
8 | import com.lvkang.skin.ktx.pathName
9 | import com.lvkang.skin.listener.SkinLoadListener
10 | import com.lvkang.skin.obsreve.SkinObserverable
11 | import com.lvkang.skin.resource.*
12 | import com.lvkang.skin.resource.SkinLoadStrategyEnum
13 | import com.lvkang.skin.resource.strategy.AbstractSkinLoadAssetsImpl
14 | import com.lvkang.skin.resource.strategy.AbstractSkinLoadStorageImpl
15 | import com.lvkang.skin.resource.strategy.AbstractSkinLoadNoneImpl
16 | import com.lvkang.skin.resource.strategy.AbstractSkinLoadZipImpl
17 | import com.lvkang.skin.util.SkinLog
18 | import kotlinx.coroutines.*
19 |
20 | @Suppress("DEPRECATION")
21 | object SkinManager : SkinObserverable() {
22 |
23 | private lateinit var application: Application
24 | private val inflaters = arrayListOf()
25 | private val startegy = mutableMapOf()
26 | private var isAutoLoadSkin = true
27 |
28 | fun init(context: Application): SkinManager {
29 | application = context
30 | return this
31 | }
32 |
33 | /** 自定义 View 时,可选择添加一个{@link SkinLayoutInflater} */
34 | fun addInflaters(inflater: SkinLayoutInflater): SkinManager {
35 | inflaters.add(inflater)
36 | return this
37 | }
38 |
39 |
40 | fun getInflaters(): ArrayList {
41 | return inflaters
42 | }
43 |
44 | /** 设置是否使用非继承的方式实现换肤,默认为 false */
45 | fun setAutoLoadSkin(isAutoLoadSkin: Boolean): SkinManager {
46 | this.isAutoLoadSkin = isAutoLoadSkin
47 | return this
48 | }
49 |
50 | /** 初始化完成后必须调用 */
51 | fun build() {
52 | startegy[SkinLoadStrategyEnum.SKIN_LOADER_STARTEGY_STORAGE.name] = AbstractSkinLoadStorageImpl()
53 | startegy[SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_NONE.name] = AbstractSkinLoadNoneImpl()
54 | startegy[SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_ASSETS.name] =
55 | AbstractSkinLoadAssetsImpl()
56 | startegy[SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_ZIP.name] = AbstractSkinLoadZipImpl()
57 |
58 | if (isAutoLoadSkin) application.registerActivityLifecycleCallbacks(SkinActivityLifecycle())
59 | val name = SkinPreUtils.getSkinName()
60 | val path = SkinPreUtils.getSkinPath()
61 | val loadStrategy = SkinPreUtils.getSkinStrategy()
62 | if (name != null && loadStrategy != null) {
63 | val strategy = getStrategyType(loadStrategy)
64 | if (strategy != null) {
65 | when (strategy) {
66 | SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_NONE -> loadNone()
67 | SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_ASSETS -> {
68 | loadAssetsSkin(name, isRepeat = true)
69 | }
70 | SkinLoadStrategyEnum.SKIN_LOADER_STARTEGY_STORAGE -> {
71 | loadStorageSkin(path, isRepeat = true)
72 | }
73 | SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_ZIP -> {
74 | loadZipSkin(path, isRepeat = true)
75 | }
76 | }
77 | return
78 | }
79 | }
80 | loadNone()
81 | }
82 |
83 |
84 | /** 加载默认皮肤 ,即无皮肤 */
85 | fun loadNone(skinLoadListener: SkinLoadListener? = null) {
86 | val none = "none"
87 | val strategy = SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_NONE
88 | val skinLoaderStrategy = startegy[strategy.name]
89 | skinLoaderStrategy?.loadSkin(none)
90 | loadSkin(none, none, strategy, skinLoadListener)
91 | }
92 |
93 | /**
94 | * 加载资源文件夹下的皮肤
95 | * @param name 资源文件名
96 | * @param skinLoadListener 回调
97 | * @param isRepeat false 表示要加载的 skin 和当前使用的相同时不重复加载
98 | */
99 | fun loadAssetsSkin(
100 | name: String,
101 | skinLoadListener: SkinLoadListener? = null,
102 | isRepeat: Boolean = false,
103 | ) {
104 | val strategy = SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_ASSETS
105 | if (compareSkin(name, strategy) && (!isRepeat)) {
106 | SkinLog.log("Repeat loading")
107 | skinLoadListener?.loadRepeat()
108 | return
109 | }
110 | val skinLoaderStrategy = startegy[strategy.name]
111 |
112 | CoroutineScope(Dispatchers.IO).launch {
113 | val skinPath = skinLoaderStrategy?.loadSkin(name)
114 | launch(Dispatchers.Main) {
115 | skinPath?.run {
116 | loadSkin(skinPath, name, strategy, skinLoadListener)
117 | } ?: kotlin.run {
118 | skinLoadListener?.loadSkinFailure("load failure")
119 | }
120 | }
121 | }
122 | }
123 |
124 | /**
125 | * 加载内部存储下的皮肤,必须是沙箱路径
126 | * @param path skin 绝对路径
127 | * @param skinLoadListener 回调
128 | * @param isRepeat false 表示要加载的 skin 和当前使用的相同时不重复加载
129 | */
130 | fun loadStorageSkin(
131 | path: String,
132 | skinLoadListener: SkinLoadListener? = null,
133 | isRepeat: Boolean = false
134 | ) {
135 | val strategy = SkinLoadStrategyEnum.SKIN_LOADER_STARTEGY_STORAGE
136 | val name = pathName(path)
137 | if (compareSkin(name, strategy) && (!isRepeat)) {
138 | SkinLog.log("Repeat loading")
139 | skinLoadListener?.loadRepeat()
140 | return
141 | }
142 | val skinLoaderStrategy = startegy[strategy.name]
143 | CoroutineScope(Dispatchers.IO).launch {
144 | val skinPath = skinLoaderStrategy?.loadSkin(path)
145 | launch(Dispatchers.Main) {
146 | skinPath?.run {
147 | loadSkin(skinPath, name, strategy, skinLoadListener)
148 | } ?: kotlin.run {
149 | skinLoadListener?.loadSkinFailure("load failure")
150 | }
151 | }
152 | }
153 | }
154 |
155 | /**
156 | * 加载内部存储下的 zpi 皮肤文件,必须是沙箱路径
157 | * @param path skin 绝对路径
158 | * @param password zip 文件密码,没有可不传
159 | * @param skinLoadListener 回调
160 | * @param isRepeat false 表示要加载的 skin 和当前使用的相同时不重复加载
161 | */
162 | fun loadZipSkin(
163 | path: String,
164 | password: String? = null,
165 | skinLoadListener: SkinLoadListener? = null,
166 | isRepeat: Boolean = false
167 | ) {
168 | val strategy = SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_ZIP
169 | val name = pathName(path)
170 | if (compareSkin(name, strategy) && (!isRepeat)) {
171 | SkinLog.log("Repeat loading")
172 | skinLoadListener?.loadRepeat()
173 | return
174 | }
175 | val skinLoaderStrategy = startegy[strategy.name]
176 | CoroutineScope(Dispatchers.IO).launch {
177 | val skinPath = skinLoaderStrategy?.loadSkin(path, password)
178 | launch(Dispatchers.Main) {
179 | skinPath?.run {
180 | loadSkin(skinPath, name, strategy, skinLoadListener)
181 | } ?: kotlin.run {
182 | skinLoadListener?.loadSkinFailure("load failure")
183 | }
184 | }
185 | }
186 | }
187 |
188 | private fun loadSkin(
189 | skinPath: String,
190 | name: String,
191 | strategyEnum: SkinLoadStrategyEnum,
192 | skinLoadListener: SkinLoadListener?
193 | ) {
194 | notifyUpdateSkin()
195 | SkinPreUtils.saveSkinStatus(skinPath, name, strategyEnum.name)
196 | skinLoadListener?.loadSkinSucess()
197 | }
198 |
199 |
200 | private fun compareSkin(skinName: String, strategyEnum: SkinLoadStrategyEnum): Boolean {
201 | val name = SkinPreUtils.getSkinName()
202 | val loadStrategy = SkinPreUtils.getSkinStrategy()
203 | if (name == skinName && strategyEnum.name == loadStrategy) {
204 | return true
205 | }
206 | return false
207 | }
208 |
209 |
210 | private fun notifyUpdateSkin() {
211 | SkinManager.notifyUpDataSkin()
212 | }
213 |
214 |
215 | fun getApplication(): Application {
216 | return application
217 | }
218 |
219 | /**
220 | * @return true 表示当前为默认皮肤
221 | */
222 | fun getIsDefault(): Boolean {
223 | return SkinCompatResources.isDefaultSkin
224 | }
225 |
226 | /**
227 | * @param strategy 策略名称
228 | * 获取加载策略
229 | */
230 | private fun getStrategyType(strategy: String): SkinLoadStrategyEnum? {
231 | SkinLoadStrategyEnum.values().forEach {
232 | if (it.name == strategy) {
233 | return it
234 | }
235 | }
236 | return null
237 | }
238 |
239 |
240 | fun getContext(): Context {
241 | return application
242 | }
243 |
244 | }
245 |
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/app/SkinActivityLifecycle.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.app
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.content.Context
6 | import android.os.Bundle
7 | import android.util.Log
8 | import android.view.LayoutInflater
9 | import androidx.core.view.LayoutInflaterCompat
10 | import com.lvkang.skin.SkinManager
11 | import com.lvkang.skin.factory.SkinCompateFactory
12 | import com.lvkang.skin.obsreve.SkinObserver
13 | import com.lvkang.skin.util.SkinLog
14 | import java.lang.Exception
15 | import java.util.*
16 |
17 | /**
18 | * @name SkinActivityLifecycle
19 | * @package com.lvkang.skin.app
20 | * @author 345 QQ:1831712732
21 | * @time 2020/12/13 21:40
22 | * @description 监听 Activity 的创建,以实现换肤
23 | */
24 | class SkinActivityLifecycle : Application.ActivityLifecycleCallbacks {
25 |
26 |
27 | private val weekDelegateMap by lazy { WeakHashMap() }
28 | private val weekObserverMap by lazy { WeakHashMap() }
29 |
30 |
31 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
32 | (activity as? SkinCompatActivity)?.run {
33 | } ?: kotlin.run {
34 | installLayoutFactory(activity)
35 | Log.e("---345--->", "111111111111111");
36 | }
37 |
38 | }
39 |
40 | override fun onActivityStarted(activity: Activity) = Unit
41 |
42 | override fun onActivityResumed(activity: Activity) {
43 | (activity as? SkinCompatActivity)?.run {
44 | } ?: kotlin.run {
45 | val lazyObserver = getLazyObserver(activity)
46 | SkinManager.addSkinObserver(lazyObserver)
47 | }
48 | }
49 |
50 | override fun onActivityPaused(activity: Activity) = Unit
51 |
52 | override fun onActivityStopped(activity: Activity) = Unit
53 |
54 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
55 |
56 | override fun onActivityDestroyed(activity: Activity) {
57 | (activity as? SkinCompatActivity)?.run {
58 | } ?: kotlin.run {
59 | val lazyObserver = getLazyObserver(activity)
60 | SkinManager.removeSkinObserver(lazyObserver)
61 | }
62 |
63 | }
64 |
65 | private fun installLayoutFactory(context: Context) {
66 | try {
67 | //这里不能同通过 LayoutInflaterCompat 设置 factory2
68 | //因为此时的 activity 已经在 setContentView 方法这种设置过 fractory2 了
69 | //factory2不允许重复设置,所以这里需要通过反射重新设置
70 | // val skinFactory = getSkinFactory(context)
71 | // LayoutInflaterCompat.setFactory2((context as Activity).layoutInflater, skinFactory)
72 | val layoutInflater = LayoutInflater.from(context)
73 | val inflaterCompat = LayoutInflaterCompat::class.java
74 | val inflater = LayoutInflater::class.java
75 |
76 | val sCheckedFiled = inflaterCompat.getDeclaredField("sCheckedField")
77 | sCheckedFiled.isAccessible = true
78 | sCheckedFiled.set(layoutInflater, false)
79 |
80 | val skinFactory = getSkinFactory(context)
81 | val mFactory = inflater.getDeclaredField("mFactory")
82 | val mFactory2 = inflater.getDeclaredField("mFactory2")
83 | mFactory.isAccessible = true
84 | mFactory2.isAccessible = true
85 |
86 | mFactory.set(layoutInflater, skinFactory)
87 | mFactory2.set(layoutInflater, skinFactory)
88 |
89 | } catch (e: Exception) {
90 | e.printStackTrace()
91 | SkinLog.log("A factory has already been set on this LayoutInflater")
92 | }
93 | }
94 |
95 | private fun getLazyObserver(context: Context): LazySkinObserver {
96 | var lazySkinObserver = weekObserverMap[context]
97 | if (lazySkinObserver == null) {
98 | lazySkinObserver = LazySkinObserver(context)
99 | weekObserverMap[context] = lazySkinObserver
100 | }
101 | return lazySkinObserver
102 | }
103 |
104 | private fun getSkinFactory(context: Context): SkinCompateFactory {
105 | var skinCompateFactory = weekDelegateMap[context]
106 | if (skinCompateFactory == null) {
107 | skinCompateFactory = SkinCompateFactory()
108 | weekDelegateMap[context] = skinCompateFactory
109 | }
110 | return skinCompateFactory
111 | }
112 |
113 |
114 | inner class LazySkinObserver(val context: Context) : SkinObserver {
115 |
116 | override fun applySkin() = updataSkin()
117 |
118 | private fun updataSkin() {
119 | val skinFactory = getSkinFactory(context)
120 | skinFactory.applySkin()
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/app/SkinCompatActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.app
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.core.view.LayoutInflaterCompat
6 | import com.lvkang.skin.SkinManager
7 | import com.lvkang.skin.factory.SkinCompateFactory
8 | import com.lvkang.skin.obsreve.SkinObserver
9 |
10 | /**
11 | * @name SkinCompatActivity
12 | * @package com.lvkang.skin.app
13 | * @author 345 QQ:1831712732
14 | * @time 2020/11/27 23:43
15 | * @description
16 | */
17 | open class SkinCompatActivity : AppCompatActivity(), SkinObserver {
18 |
19 |
20 | private val skinFactory by lazy { SkinCompateFactory() }
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | LayoutInflaterCompat.setFactory2(layoutInflater, skinFactory)
24 | super.onCreate(savedInstanceState)
25 | }
26 |
27 | override fun onResume() {
28 | super.onResume()
29 | SkinManager.addSkinObserver(this)
30 | }
31 |
32 | override fun onDestroy() {
33 | super.onDestroy()
34 | SkinManager.removeSkinObserver(this)
35 | }
36 |
37 | override fun applySkin() {
38 | skinFactory.applySkin()
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/config/SkinKey.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.config
2 |
3 | /**
4 | * @name SkinConfig
5 | * @package com.lvkang.skin.config
6 | * @author 345 QQ:1831712732
7 | * @time 2020/11/24 23:31
8 | * @description
9 | */
10 | object SkinKey {
11 |
12 | const val TAG = "tag"
13 |
14 | const val SKIN_INFO_NAME = "skinInfo"
15 |
16 | /** 皮肤缓存 DirPath */
17 | const val SKIN_DIR_PATH = "skin_dir_path"
18 |
19 | /** 皮肤名字 */
20 | const val SKIN_NAME = "skin_name"
21 |
22 | /** 皮肤加载策略 */
23 | const val SKIN_STRATEGY = "skin_strategy"
24 |
25 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/config/SkinPreUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.config
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import com.lvkang.skin.SkinManager
6 | import java.io.File
7 |
8 |
9 | /**
10 | * @name SkinConfig
11 | * @package com.lvkang.skin.config
12 | * @author 345 QQ:1831712732
13 | * @time 2020/11/24 23:31
14 | * @description
15 | */
16 | @SuppressLint("StaticFieldLeak")
17 | object SkinPreUtils {
18 |
19 | private val context by lazy { SkinManager.getApplication() }
20 |
21 | private val skinDir by lazy { "${context.getExternalFilesDir("file")}${File.separator}" }
22 |
23 |
24 | /**
25 | * 保存当前皮肤路径
26 | */
27 | private fun saveSkinPath(skinPath: String?) {
28 | context.getSharedPreferences(SkinKey.SKIN_INFO_NAME, Context.MODE_PRIVATE)
29 | .edit()
30 | .putString(SkinKey.SKIN_DIR_PATH, skinPath)
31 | .apply()
32 | }
33 |
34 |
35 | fun getSkinPath(): String {
36 | return context.getSharedPreferences(SkinKey.SKIN_INFO_NAME, Context.MODE_PRIVATE)
37 | .getString(SkinKey.SKIN_DIR_PATH, skinDir)!!
38 | }
39 |
40 | /**
41 | * @param skinPath 皮肤路径,注意不包括皮肤名称
42 | * @param skinName 皮肤名称
43 | * @param skinStrategy 皮肤加载策略
44 | * 保存当前使用皮肤的状态
45 | */
46 | fun saveSkinStatus(skinPath: String, skinName: String, skinStrategy: String?) {
47 | saveSkinPath(skinPath)
48 | saveSkinName(skinName)
49 | saveSkinStrategy(skinStrategy)
50 | }
51 |
52 |
53 | /**
54 | * 保存当前皮肤名称
55 | */
56 | private fun saveSkinName(skinName: String?) {
57 | context.getSharedPreferences(SkinKey.SKIN_INFO_NAME, Context.MODE_PRIVATE)
58 | .edit()
59 | .putString(SkinKey.SKIN_NAME, skinName)
60 | .apply()
61 | }
62 |
63 | /**
64 | * 获取当前皮肤名称
65 | */
66 | fun getSkinName(): String? {
67 | return context.getSharedPreferences(SkinKey.SKIN_INFO_NAME, Context.MODE_PRIVATE)
68 | .getString(SkinKey.SKIN_NAME, null)
69 | }
70 |
71 |
72 | /**
73 | * 保存当前皮肤加载策略
74 | */
75 | private fun saveSkinStrategy(skinStrategy: String?) {
76 | context.getSharedPreferences(SkinKey.SKIN_INFO_NAME, Context.MODE_PRIVATE)
77 | .edit()
78 | .putString(SkinKey.SKIN_STRATEGY, skinStrategy)
79 | .apply()
80 | }
81 |
82 | /**
83 | * 获取当前皮肤加载策略
84 | */
85 | fun getSkinStrategy(): String? {
86 | return context.getSharedPreferences(SkinKey.SKIN_INFO_NAME, Context.MODE_PRIVATE)
87 | .getString(SkinKey.SKIN_STRATEGY, null)
88 | }
89 |
90 | /**
91 | * 清空皮肤路径
92 | */
93 | fun clearSkinInfo() {
94 | saveSkinPath(null)
95 | saveSkinName(null)
96 | saveSkinStrategy(null)
97 | }
98 |
99 | /**
100 | * 添加一个标记
101 | */
102 | fun setTag(boolean: Boolean) {
103 | context.getSharedPreferences(SkinKey.TAG, Context.MODE_PRIVATE)
104 | .edit()
105 | .putBoolean(SkinKey.TAG, boolean)
106 | .apply()
107 | }
108 |
109 | /**
110 | * 获取标记
111 | */
112 | fun getTag(): Boolean {
113 | return context.getSharedPreferences(SkinKey.TAG, Context.MODE_PRIVATE)
114 | .getBoolean(SkinKey.TAG, false)
115 | }
116 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/factory/SkinCompateFactory.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.factory
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.util.Log
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import com.lvkang.skin.inflater.SkinViewInflater
9 | import com.lvkang.skin.wedget.SkinCompatSupportable
10 | import java.lang.ref.WeakReference
11 | import java.util.concurrent.CopyOnWriteArrayList
12 |
13 | /**
14 | * @author 345 QQ:1831712732
15 | * @name SkinCompateFactory
16 | * @package com.lvkang.skin.factory
17 | * @time 2020/11/27 23:39
18 | * @description 拦截 View 的创建
19 | */
20 | class SkinCompateFactory : LayoutInflater.Factory2 {
21 |
22 | private val mSkinHelpers = CopyOnWriteArrayList>()
23 |
24 | private val mSkinCompatViewInflater by lazy { SkinViewInflater() }
25 |
26 | override fun onCreateView(
27 | parent: View?,
28 | name: String,
29 | context: Context,
30 | attrs: AttributeSet
31 | ): View? {
32 | return onCreateView(name, context, attrs)
33 | }
34 |
35 | override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
36 | val view = mSkinCompatViewInflater.createView(name, context, attrs)
37 | if (view is SkinCompatSupportable) {
38 | mSkinHelpers.add(WeakReference(view))
39 | }
40 | return view
41 | }
42 |
43 | fun applySkin() {
44 | if (mSkinHelpers.isNotEmpty()) {
45 | mSkinHelpers.forEach {
46 | it?.run {
47 | get()?.run {
48 | applySkin()
49 | }
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/inflater/SkinAppCompatViewInflater.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.inflater
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import androidx.appcompat.widget.LinearLayoutCompat
7 | import com.lvkang.skin.util.SkinLog
8 | import com.lvkang.skin.wedget.android.FrameLayoutX
9 | import com.lvkang.skin.wedget.android.RelativeLayoutX
10 | import com.lvkang.skin.wedget.android.ViewX
11 | import com.lvkang.skin.wedget.androidx.*
12 | import com.lvkang.skin.wedget.androidx.cardview.CardViewX
13 |
14 | /**
15 | * @name SkinAppCompatViewInflater
16 | * @package com.lvkang.skin.inflater
17 | * @author 345 QQ:1831712732
18 | * @time 2020/12/01 23:29
19 | * @description
20 | */
21 | class SkinAppCompatViewInflater : SkinLayoutInflater {
22 | @Suppress("PrivatePropertyName")
23 | private val TAG = "SkinAppCompatViewInflat"
24 | override fun createView(context: Context, name: String, attres: AttributeSet): View? {
25 | return createViewFrom(context, name, attres)
26 | }
27 |
28 | private fun createViewFrom(context: Context, name: String, attres: AttributeSet): View? {
29 | when (name) {
30 | "View" -> return ViewX(context, attres)
31 | "ImageView" -> return ImageViewX(context, attres)
32 | "Button" -> return ButtonX(context, attres)
33 | "EditText" -> return EditTextX(context, attres)
34 | "TextView" -> return TextViewX(context, attres)
35 | "FrameLayout" -> return FrameLayoutX(context, attres)
36 | "RelativeLayout" -> return RelativeLayoutX(context, attres)
37 | "ScrollView" -> return NestedScrollViewX(context, attres)
38 | "androidx.appcompat.widget.AppCompatImageView" -> return ImageViewX(context, attres)
39 | "androidx.appcompat.widget.AppCompatButton" -> return ButtonX(context, attres)
40 | "androidx.appcompat.widget.AppCompatTextView" -> return TextViewX(context, attres)
41 | "androidx.appcompat.widget.AppCompatEditText" -> return EditTextX(context, attres)
42 | "androidx.cardview.widget.CardView" -> return CardViewX(context, attres)
43 | "androidx.core.widget.NestedScrollView" -> {
44 | return NestedScrollViewX(context, attres)
45 | }
46 | "androidx.constraintlayout.widget.ConstraintLayout" -> {
47 | return ConstraintLayoutX(context, attres)
48 | }
49 | "androidx.appcompat.widget.LinearLayoutCompat" -> {
50 | return LinearLayoutCompat(context, attres)
51 | }
52 | }
53 |
54 | return null
55 | }
56 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/inflater/SkinLayoutInflater.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.inflater
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import androidx.annotation.NonNull
7 |
8 | /**
9 | * @name SkinLayoutInflater
10 | * @package com.lvkang.skin.inflater
11 | * @author 345 QQ:1831712732
12 | * @time 2020/11/29 14:18
13 | * @description
14 | */
15 | interface SkinLayoutInflater {
16 | fun createView(@NonNull context: Context, name: String, @NonNull attres: AttributeSet): View?
17 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/inflater/SkinViewInflater.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.inflater
2 |
3 | import android.R
4 | import android.annotation.SuppressLint
5 | import android.content.Context
6 | import android.content.ContextWrapper
7 | import android.os.Build
8 | import android.util.AttributeSet
9 | import android.view.InflateException
10 | import android.view.View
11 | import androidx.core.view.ViewCompat
12 | import com.lvkang.skin.SkinManager
13 | import java.lang.reflect.Constructor
14 | import java.lang.reflect.InvocationTargetException
15 | import java.lang.reflect.Method
16 |
17 | /**
18 | * @name SkinCompatViewInflater
19 | * @package com.lvkang.skin.inflater
20 | * @author 345 QQ:1831712732
21 | * @time 2020/11/29 14:41
22 | * @description
23 | */
24 | open class SkinViewInflater {
25 |
26 | private val mConstructorArgs = arrayOfNulls(2)
27 | private val sClassPrefixList = arrayOf(
28 | "android.widget.",
29 | "android.view.",
30 | "android.webkit."
31 | )
32 | private val sConstructorMap: MutableMap> = mutableMapOf()
33 | private val sConstructorSignature: Array> = arrayOf(
34 | Context::class.java, AttributeSet::class.java
35 | )
36 | private val sOnClickAttrs = intArrayOf(R.attr.onClick)
37 |
38 |
39 | fun createView(name: String, context: Context, attrs: AttributeSet): View? {
40 | //从自定义的 Inflater 中进行加載
41 | var view = createViewFromInflater(context, name, attrs)
42 |
43 | if (view == null) {
44 | view = createViewFromTag(context, name, attrs)
45 | }
46 |
47 | if (view != null) {
48 | // If we have created a view, check its android:onClick
49 | checkOnClickListener(view, attrs)
50 | }
51 | return view
52 | }
53 |
54 |
55 | private fun createViewFromInflater(context: Context, name: String, attrs: AttributeSet): View? {
56 | var view: View?
57 | SkinManager.getInflaters().forEach {
58 | view = it.createView(context, name, attrs)
59 | if (view != null) return view
60 | }
61 | return null
62 | }
63 |
64 |
65 | private fun createViewFromTag(context: Context, n: String, attrs: AttributeSet): View? {
66 | var name = n
67 | if (name == "view") {
68 | name = attrs.getAttributeValue(null, "class")
69 | }
70 | return try {
71 | mConstructorArgs[0] = context
72 | mConstructorArgs[1] = attrs
73 | if (-1 == name.indexOf('.')) {
74 | for (s in sClassPrefixList) {
75 | val view: View? = createViewByPrefix(context, name, s)
76 | if (view != null) {
77 | return view
78 | }
79 | }
80 | null
81 | } else {
82 | createViewByPrefix(context, name, null)
83 | }
84 | } catch (e: Exception) {
85 | // We do not want to catch these, lets return null and let the actual LayoutInflater
86 | // try
87 | null
88 | } finally {
89 | // Don't retain references on context.
90 | mConstructorArgs[0] = null
91 | mConstructorArgs[1] = null
92 | }
93 | }
94 |
95 | @Throws(ClassNotFoundException::class, InflateException::class)
96 | private fun createViewByPrefix(context: Context, name: String, prefix: String?): View? {
97 | var constructor: Constructor? = sConstructorMap[name]
98 | return try {
99 | if (constructor == null) {
100 | // Class not found in the cache, see if it's real, and try to add it
101 | val clazz = Class.forName(
102 | if (prefix != null) prefix + name else name,
103 | false,
104 | context.classLoader
105 | ).asSubclass(View::class.java)
106 | constructor = clazz.getConstructor(*sConstructorSignature)
107 | sConstructorMap[name] = constructor
108 | }
109 | constructor?.isAccessible = true
110 | constructor?.newInstance(*mConstructorArgs)
111 | } catch (e: java.lang.Exception) {
112 | // We do not want to catch these, lets return null and let the actual LayoutInflater
113 | // try
114 | null
115 | }
116 | }
117 |
118 | @SuppressLint("ObsoleteSdkInt")
119 | private fun checkOnClickListener(view: View, attrs: AttributeSet) {
120 | val context = view.context
121 | if (context !is ContextWrapper ||
122 | Build.VERSION.SDK_INT >= 15 && !ViewCompat.hasOnClickListeners(view)
123 | ) {
124 | // Skip our compat functionality if: the Context isn't a ContextWrapper, or
125 | // the view doesn't have an OnClickListener (we can only rely on this on API 15+ so
126 | // always use our compat code on older devices)
127 | return
128 | }
129 |
130 | val a = context.obtainStyledAttributes(attrs, sOnClickAttrs)
131 | val handlerName = a.getString(0)
132 | if (handlerName != null) {
133 | view.setOnClickListener(DeclaredOnClickListener(view, handlerName))
134 | }
135 | a.recycle()
136 | }
137 |
138 | /**
139 | * An implementation of OnClickListener that attempts to lazily load a
140 | * named click handling method from a parent or ancestor context.
141 | */
142 | private class DeclaredOnClickListener(
143 | private val mHostView: View,
144 | private val mMethodName: String
145 | ) :
146 | View.OnClickListener {
147 | private var mResolvedMethod: Method? = null
148 | private var mResolvedContext: Context? = null
149 | override fun onClick(v: View) {
150 | if (mResolvedMethod == null) {
151 | resolveMethod(mHostView.context, mMethodName)
152 | }
153 | try {
154 | mResolvedMethod!!.invoke(mResolvedContext, v)
155 | } catch (e: IllegalAccessException) {
156 | throw IllegalStateException(
157 | "Could not execute non-public method for android:onClick", e
158 | )
159 | } catch (e: InvocationTargetException) {
160 | throw IllegalStateException(
161 | "Could not execute method for android:onClick", e
162 | )
163 | }
164 | }
165 |
166 | private fun resolveMethod(mContext: Context?, name: String) {
167 | var context = mContext
168 | while (context != null) {
169 | try {
170 | if (!context.isRestricted) {
171 | val method = context.javaClass.getMethod(
172 | mMethodName,
173 | View::class.java
174 | )
175 | mResolvedMethod = method
176 | mResolvedContext = context
177 | return
178 | }
179 | } catch (e: NoSuchMethodException) {
180 | // Failed to find method, keep searching up the hierarchy.
181 | }
182 | context = if (context is ContextWrapper) {
183 | context.baseContext
184 | } else {
185 | // Can't search up the hierarchy, null out and fail.
186 | null
187 | }
188 | }
189 | val id = mHostView.id
190 | val idText =
191 | if (id == View.NO_ID) "" else " with id '" + mHostView.context.resources.getResourceEntryName(
192 | id
193 | ) + "'"
194 | throw IllegalStateException(
195 | "Could not find method " + mMethodName
196 | + "(View) in a parent or ancestor Context for android:onClick "
197 | + "attribute defined on view " + mHostView.javaClass + idText
198 | )
199 | }
200 | }
201 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/ktx/SkinHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.ktx
2 |
3 | import android.content.res.TypedArray
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import androidx.annotation.AttrRes
7 | import androidx.annotation.StyleRes
8 | import androidx.annotation.StyleableRes
9 | import com.lvkang.skin.SkinManager
10 | import com.lvkang.skin.config.SkinPreUtils
11 | import java.io.File
12 | import java.lang.Exception
13 |
14 | /**
15 | * @name SkinHelper
16 | * @package com.lvkang.skin.ktx
17 | * @author 345 QQ:1831712732
18 | * @time 2020/11/29 16:40
19 | * @description
20 | */
21 |
22 | inline fun obtainStyledAttributes(
23 | view: View,
24 | set: AttributeSet?,
25 | @StyleableRes attrs: IntArray,
26 | @AttrRes defstyleAttr: Int,
27 | @StyleRes defStyleRes: Int,
28 | crossinline block: (TypedArray) -> Unit
29 | ) {
30 | val a =
31 | view.context.obtainStyledAttributes(set, attrs, defstyleAttr, defStyleRes)
32 | tryCatch { block(a) }
33 | a.recycle()
34 | }
35 |
36 | inline fun tryCatch(block: () -> Unit) {
37 | try {
38 | block()
39 | } catch (e: Exception) {
40 | e.printStackTrace()
41 | }
42 | }
43 |
44 | /**
45 | * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
46 | */
47 | fun px2dip(pxValue: Float): Float {
48 | val scale: Float = SkinManager.getContext().resources.displayMetrics.density
49 | return (pxValue / scale + 0.5f)
50 | }
51 |
52 |
53 | /**
54 | * 文件是否存在,true 表示存在
55 | */
56 | fun isFile(filePath: String): Boolean {
57 | if (!File(filePath).exists()) {
58 | return false
59 | }
60 | return true
61 | }
62 |
63 | fun pathName(path: String): String {
64 | val name = path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf("."))
65 | val suffix = path.substring(path.lastIndexOf(".") + 1, path.length)
66 | return "$name.$suffix"
67 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/listener/SkinLoadListener.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.listener
2 |
3 | /**
4 | * @name SkinLoadListener
5 | * @package com.lvkang.skin
6 | * @author 345 QQ:1831712732
7 | * @time 2020/12/07 22:31
8 | * @description
9 | */
10 | interface SkinLoadListener {
11 |
12 | /** 换肤成功 */
13 | fun loadSkinSucess()
14 |
15 | /** 加载失败 */
16 | fun loadSkinFailure(error: String)
17 |
18 | /** 重复加载 */
19 | fun loadRepeat()
20 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/obsreve/SkinObserver.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.obsreve
2 |
3 | /**
4 | * @name SkinObserver
5 | * @package com.lvkang.skin.obsreve
6 | * @author 345 QQ:1831712732
7 | * @time 2020/12/01 22:31
8 | * @description
9 | */
10 | interface SkinObserver {
11 | fun applySkin()
12 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/obsreve/SkinObserverable.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.obsreve
2 |
3 | /**
4 | * @name SkinObserverable
5 | * @package com.lvkang.skin.obsreve
6 | * @author 345 QQ:1831712732
7 | * @time 2020/12/01 22:37
8 | * @description
9 | */
10 | open class SkinObserverable {
11 | private val observers by lazy {
12 | arrayListOf()
13 | }
14 |
15 | fun addSkinObserver(skinObserver: SkinObserver) {
16 | //如果不包含此观察者,则添加
17 | if (!observers.contains(skinObserver))
18 | observers.add(skinObserver)
19 | }
20 |
21 | fun removeSkinObserver(skinObserver: SkinObserver) {
22 | observers.remove(skinObserver)
23 | }
24 |
25 | fun clearSkinObserver() {
26 | observers.clear()
27 | }
28 |
29 | fun notifyUpDataSkin() {
30 | observers.forEach {
31 | it.applySkin()
32 | }
33 | }
34 |
35 | fun skinObserveSize(): Int {
36 | return observers.size
37 | }
38 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/resource/AbstractSkinLoadStrategy.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.resource
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.Drawable
5 | import com.lvkang.skin.SkinManager
6 | import java.io.File
7 |
8 | /**
9 | * @name SkinLoaderStrategy
10 | * @package com.lvkang.skin.resource
11 | * @author 345 QQ:1831712732
12 | * @time 2020/12/07 00:24
13 | * @description 皮肤包加载策略
14 | */
15 |
16 |
17 | abstract class AbstractSkinLoadStrategy {
18 |
19 | val skinFileDir = "${SkinManager.getApplication().getExternalFilesDir("file")}${File.separator}"
20 |
21 | /**
22 | * 加载皮肤
23 | * @return 不等于 null 且 length 大于0 表示皮肤加载成功
24 | * length == 0 表示使用的是 SKIN_LOADER_STRATEGY_NONE,即为没有加载皮肤,使用 app 内部资源
25 | * 等于 null 表示皮肤加载失败
26 | */
27 | abstract fun loadSkin(vararg any: String?): String?
28 |
29 | /**
30 | * @return 加载策略
31 | */
32 | abstract fun getType(): SkinLoadStrategyEnum
33 |
34 | /**
35 | *@return 皮肤包中资源的名称
36 | */
37 | open fun getSkinResName(): String? = null
38 |
39 | /**
40 | * 通过重写此方法可返回自定义颜色
41 | * @return color
42 | */
43 | open fun getColor(context: Context, skinName: String, resId: Int): Int =
44 | SkinCompatResources.NOT_ID
45 |
46 | /**
47 | * 通过重写此方法可返回自定义drawable
48 | * @return drawable
49 | */
50 | open fun getDrawable(context: Context, skinName: String, resId: Int): Drawable? = null
51 |
52 | open fun getDimension(context: Context, skinName: String, resId: Int): Float? = null
53 |
54 | open fun getString(context: Context, skinName: String, resId: Int): String? = null
55 |
56 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/resource/SkinCompatResources.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.resource
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.pm.PackageManager
5 | import android.content.res.AssetManager
6 | import android.content.res.ColorStateList
7 | import android.content.res.Resources
8 | import android.graphics.drawable.Drawable
9 | import android.util.Log
10 | import androidx.core.content.res.ResourcesCompat
11 | import com.lvkang.skin.SkinManager
12 | import com.lvkang.skin.ktx.tryCatch
13 | import com.lvkang.skin.util.SkinLog
14 |
15 | /**
16 | * @name SkinResource
17 | * @package com.lvkang.skin
18 | * @author 345 QQ:1831712732
19 | * @time 2020/11/24 23:31
20 | * @description 皮肤的资源管理器
21 | */
22 | @SuppressLint("DiscouragedPrivateApi")
23 | object SkinCompatResources {
24 | const val NOT_ID = 0
25 | private lateinit var resources: Resources
26 | private lateinit var packageName: String
27 | private lateinit var skinName: String
28 | private lateinit var loadStrategyAbstract: AbstractSkinLoadStrategy
29 | private val context by lazy { SkinManager.getContext() }
30 | var isDefaultSkin = true
31 |
32 |
33 | fun resetSkin(
34 | resources: Resources,
35 | loadStrategyAbstract: AbstractSkinLoadStrategy
36 | ) {
37 | isDefaultSkin = true
38 | this.resources = resources
39 | this.packageName = ""
40 | this.skinName = ""
41 | this.loadStrategyAbstract = loadStrategyAbstract
42 | }
43 |
44 |
45 | fun setupSkin(
46 | resources: Resources,
47 | packageName: String,
48 | skinName: String,
49 | loadStrategyAbstract: AbstractSkinLoadStrategy
50 | ) {
51 | this.resources = resources
52 | this.packageName = packageName
53 | this.skinName = skinName
54 | this.loadStrategyAbstract = loadStrategyAbstract
55 | isDefaultSkin = false
56 | }
57 |
58 |
59 | /**
60 | * 获取 String
61 | */
62 | fun getString(resId: Int): String? {
63 | tryCatch {
64 | val string = loadStrategyAbstract.getString(context, skinName, resId)
65 | if (string != null) return string
66 | if (!isDefaultSkin) {
67 | val skinResId = getSkinResId(resId)
68 | if (skinResId != NOT_ID)
69 | return resources.getString(skinResId)
70 | } else {
71 | return context.resources.getString(resId)
72 | }
73 | }
74 | return null
75 | }
76 |
77 | /**
78 | * 获取 Dimension
79 | */
80 | fun getDimension(resId: Int): Float? {
81 | tryCatch {
82 | val float = loadStrategyAbstract.getDimension(context, skinName, resId)
83 | if (float != null) return float
84 | if (!isDefaultSkin) {
85 | val skinResId = getSkinResId(resId)
86 | if (skinResId != NOT_ID)
87 | return resources.getDimension(skinResId)
88 | } else {
89 | return context.resources.getDimension(resId)
90 | }
91 | }
92 | return null
93 | }
94 |
95 | /**
96 | * 获取 Drawable
97 | */
98 | fun getDrawable(resId: Int): Drawable? {
99 | tryCatch {
100 | val drawable = loadStrategyAbstract.getDrawable(context, skinName, resId)
101 | if (drawable != null) return drawable
102 | if (!isDefaultSkin) {
103 | val skinResId = getSkinResId(resId)
104 | if (skinResId != NOT_ID)
105 | return ResourcesCompat.getDrawable(resources, skinResId, null)
106 | } else {
107 | return ResourcesCompat.getDrawable(context.resources, resId, null)
108 | }
109 | }
110 | return null
111 | }
112 |
113 | /** 获取 Color */
114 | fun getColor(resId: Int): Int? {
115 | tryCatch {
116 | val color = loadStrategyAbstract.getColor(context, skinName, resId)
117 | if (color != NOT_ID) return color
118 | if (!isDefaultSkin) {
119 | val skinResId = getSkinResId(resId)
120 | if (skinResId != NOT_ID)
121 | return ResourcesCompat.getColor(resources, skinResId, null)
122 | } else {
123 | return ResourcesCompat.getColor(context.resources, resId, null)
124 | }
125 | }
126 | return null
127 | }
128 |
129 | fun getColorStateList(resId: Int): ColorStateList? {
130 | tryCatch {
131 | if (!isDefaultSkin) {
132 | val skinResId = getSkinResId(resId)
133 | if (skinResId != NOT_ID)
134 | return ResourcesCompat.getColorStateList(resources, skinResId, null)
135 | } else {
136 | return ResourcesCompat.getColorStateList(context.resources, resId, null)
137 | }
138 | }
139 | return null
140 | }
141 |
142 | private fun getSkinResId(resId: Int): Int {
143 | return try {
144 | val resName =
145 | loadStrategyAbstract.getSkinResName() ?: context.resources.getResourceEntryName(
146 | resId
147 | )
148 | val resType = context.resources.getResourceTypeName(resId)
149 | SkinLog.log("$resId $resName $resType")
150 | resources.getIdentifier(resName, resType, packageName)
151 | } catch (e: Exception) {
152 | SkinLog.log(e.message ?: "Not Font resId $resId")
153 | NOT_ID
154 | }
155 | }
156 |
157 | /** 获取皮肤包 resources */
158 | @SuppressLint("DiscouragedPrivateApi")
159 | fun getSkinResources(skinPath: String): Resources? {
160 | return try {
161 | val superRes = SkinManager.getApplication().resources
162 | val asset = AssetManager::class.java.newInstance()
163 | val method =
164 | AssetManager::class.java.getDeclaredMethod("addAssetPath", String::class.java)
165 | method.invoke(asset, skinPath)
166 | Resources(asset, superRes.displayMetrics, superRes.configuration)
167 | } catch (e: java.lang.Exception) {
168 | e.printStackTrace()
169 | null
170 | }
171 | }
172 |
173 | /**
174 | * 获取皮肤包名
175 | */
176 | fun getSkinPackageName(skinPath: String): String? {
177 | return SkinManager.getContext().packageManager.getPackageArchiveInfo(
178 | skinPath,
179 | PackageManager.GET_ACTIVITIES
180 | )?.packageName
181 | }
182 |
183 |
184 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/resource/SkinLoadStrategyEnum.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.resource
2 |
3 | /**
4 | * @name SkinLoadType
5 | * @package com.lvkang.skin.resource
6 | * @author 345 QQ:1831712732
7 | * @time 2020/12/07 01:07
8 | * @description
9 | */
10 | enum class SkinLoadStrategyEnum {
11 |
12 | /** 默认加载策略,即没有换肤效果 */
13 | SKIN_LOADER_STRATEGY_NONE,
14 |
15 | /** 内部存储加载策略 */
16 | SKIN_LOADER_STARTEGY_STORAGE,
17 |
18 | /** Assets 加载策略 */
19 | SKIN_LOADER_STRATEGY_ASSETS,
20 |
21 | /** 压缩包加载策略 */
22 | SKIN_LOADER_STRATEGY_ZIP,
23 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/resource/strategy/AbstractSkinLoadAssetsImpl.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.resource.strategy
2 |
3 | import com.lvkang.skin.SkinManager
4 | import com.lvkang.skin.config.SkinPreUtils
5 | import com.lvkang.skin.ktx.isFile
6 | import com.lvkang.skin.ktx.pathName
7 | import com.lvkang.skin.resource.AbstractSkinLoadStrategy
8 | import com.lvkang.skin.resource.SkinCompatResources
9 | import com.lvkang.skin.resource.SkinLoadStrategyEnum
10 | import java.io.File
11 | import java.io.IOException
12 |
13 | /**
14 | * @name SkinAssetsLoaderImpl
15 | * @package com.lvkang.skin.resource.strategy
16 | * @author 345 QQ:1831712732
17 | * @time 2020/12/07 22:24
18 | * @description Assets 加载策略,即 Assets 目录下的皮肤包
19 | */
20 |
21 | class AbstractSkinLoadAssetsImpl : AbstractSkinLoadStrategy() {
22 |
23 | override fun loadSkin(vararg any: String?): String? {
24 | val skinPath = copyCache(any[0]!!, skinFileDir)
25 | if (skinPath.isNullOrBlank()) return null
26 | val resource = SkinCompatResources.getSkinResources(skinPath)
27 | val packageName = SkinCompatResources.getSkinPackageName(skinPath)
28 | if (resource != null && packageName != null) {
29 | SkinCompatResources.setupSkin(resource, packageName, pathName(skinPath), this)
30 | return skinPath
31 | }
32 | return null
33 | }
34 |
35 | override fun getType(): SkinLoadStrategyEnum = SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_ASSETS
36 |
37 | private fun copyCache(skinName: String, cacheDir: String): String? {
38 | return try {
39 | val outFile = File(cacheDir, skinName)
40 | if (isFile(outFile.path)) {
41 | SkinPreUtils.clearSkinInfo()
42 | return outFile.path
43 | }
44 | val input = SkinManager.getContext().resources.assets.open(skinName)
45 | input.copyTo(outFile.outputStream())
46 | outFile.absolutePath
47 | } catch (e: IOException) {
48 | e.printStackTrace()
49 | null
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/resource/strategy/AbstractSkinLoadNoneImpl.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.resource.strategy
2 |
3 | import com.lvkang.skin.SkinManager
4 | import com.lvkang.skin.resource.AbstractSkinLoadStrategy
5 | import com.lvkang.skin.resource.SkinCompatResources
6 | import com.lvkang.skin.resource.SkinLoadStrategyEnum
7 |
8 | /**
9 | * @name SkinNoneLoaderImpl
10 | * @package com.lvkang.skin.resource.strategy
11 | * @author 345 QQ:1831712732
12 | * @time 2020/12/07 22:22
13 | * @description 默认加载策略,即不加载任何皮肤
14 | */
15 | class AbstractSkinLoadNoneImpl : AbstractSkinLoadStrategy() {
16 | override fun loadSkin(vararg any: String?): String? {
17 | SkinCompatResources.resetSkin(SkinManager.getContext().resources, this)
18 | return null
19 | }
20 |
21 | override fun getType(): SkinLoadStrategyEnum = SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_NONE
22 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/resource/strategy/AbstractSkinLoadStorageImpl.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.resource.strategy
2 |
3 | import com.lvkang.skin.ktx.pathName
4 | import com.lvkang.skin.resource.AbstractSkinLoadStrategy
5 | import com.lvkang.skin.resource.SkinCompatResources
6 | import com.lvkang.skin.resource.SkinLoadStrategyEnum
7 |
8 | /**
9 | * @name SkinLoadImpl
10 | * @package ccom.lvkang.skin.resource.strategy
11 | * @author 345 QQ:1831712732
12 | * @time 2020/12/07 22:27
13 | * @description 内部存储加载策略
14 | */
15 | class AbstractSkinLoadStorageImpl : AbstractSkinLoadStrategy() {
16 |
17 | override fun loadSkin(vararg any: String?): String? {
18 | val path = any[0]!!
19 | val resource = SkinCompatResources.getSkinResources(path)
20 | val packageName = SkinCompatResources.getSkinPackageName(path)
21 | if (resource != null && packageName != null) {
22 | SkinCompatResources.setupSkin(resource, packageName, pathName(path), this)
23 | return path
24 | }
25 | return null
26 | }
27 |
28 |
29 | override fun getType(): SkinLoadStrategyEnum = SkinLoadStrategyEnum.SKIN_LOADER_STARTEGY_STORAGE
30 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/resource/strategy/AbstractSkinLoadZipImpl.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.resource.strategy
2 |
3 | import com.lvkang.skin.ktx.isFile
4 | import com.lvkang.skin.ktx.pathName
5 | import com.lvkang.skin.resource.AbstractSkinLoadStrategy
6 | import com.lvkang.skin.resource.SkinCompatResources
7 | import com.lvkang.skin.resource.SkinLoadStrategyEnum
8 | import com.lvkang.skin.util.SkinLog
9 | import net.lingala.zip4j.ZipFile
10 | import java.io.File
11 |
12 |
13 | /**
14 | * @name AbstractSkinLoadZipImpl
15 | * @package com.lvkang.skin.resource.strategy
16 | * @author 345 QQ:1831712732
17 | * @time 2021/07/30 14:12
18 | * @description
19 | */
20 | class AbstractSkinLoadZipImpl : AbstractSkinLoadStrategy() {
21 |
22 | override fun loadSkin(vararg any: String?): String? {
23 | val path = any[0]!!
24 | if (!isFile(path)) {
25 | return null
26 | }
27 | //path包含.skin,说明是app初始化加载的
28 | if (path.contains(".skin")) {
29 | return loadSkin(path)
30 | }
31 | //判断该文件是否已经被解压过
32 | val skin = "${path.substring(0, path.lastIndexOf('.'))}.skin"
33 | if (isFile(skin)) {
34 | return loadSkin(skin)
35 | }
36 | val password = if (any.size > 1) any[1] else null
37 | val list = try {
38 | if (password != null) {
39 | SkinLog.log("$password")
40 | unzip(File(path), skinFileDir, password)
41 | } else {
42 | unzip(File(path), skinFileDir, null)
43 | }
44 | } catch (e: Exception) {
45 | e.printStackTrace()
46 | return null
47 | }
48 | if (list.isNotEmpty()) {
49 | return loadSkin(list[0].absolutePath)
50 | }
51 | return null
52 | }
53 |
54 | private fun loadSkin(skinPath: String): String? {
55 | val resource = SkinCompatResources.getSkinResources(skinPath)
56 | val packageName = SkinCompatResources.getSkinPackageName(skinPath)
57 | if (resource != null && packageName != null) {
58 | SkinCompatResources.setupSkin(resource, packageName, pathName(skinPath), this)
59 | return skinPath
60 | }
61 | return null
62 | }
63 |
64 |
65 | override fun getType(): SkinLoadStrategyEnum = SkinLoadStrategyEnum.SKIN_LOADER_STRATEGY_ZIP
66 |
67 | private fun unzip(zipFile: File?, dest: String, passwd: String?): MutableList {
68 | val zFile = ZipFile(zipFile)
69 |
70 | val destDir = File(dest)
71 | if (destDir.isDirectory && !destDir.exists()) {
72 | destDir.mkdir()
73 | }
74 | if (zFile.isEncrypted && passwd != null) {
75 | zFile.setPassword(passwd.toCharArray())
76 | }
77 | zFile.extractAll(dest)
78 | val headerList = zFile.fileHeaders
79 | val extractedFileList: MutableList = ArrayList()
80 | for (fileHeader in headerList) {
81 | if (!fileHeader.isDirectory) {
82 | extractedFileList.add(File(destDir, fileHeader.fileName))
83 | }
84 | }
85 | return extractedFileList.toMutableList()
86 | }
87 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/util/SkinLog.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.util
2 |
3 | import android.util.Log
4 |
5 | /**
6 | * @name SkinLog
7 | * @package com.lvkang.skin.util
8 | * @author 345 QQ:1831712732
9 | * @time 2021/07/28 18:08
10 | * @description
11 | */
12 | object SkinLog {
13 |
14 | private const val TAG = "SkinLog"
15 |
16 | const val LOG_V = "verbose"
17 | const val LOG_D = "debug"
18 | const val LOG_I = "info"
19 | const val LOG_W = "warn"
20 | const val LOG_E = "error"
21 |
22 | var defaultLongLevel = LOG_D
23 |
24 | fun log(content: String, logLevel: String = defaultLongLevel) {
25 | when (logLevel) {
26 | LOG_V -> Log.v(TAG, content)
27 | LOG_D -> Log.d(TAG, content)
28 | LOG_I -> Log.i(TAG, content)
29 | LOG_W -> Log.w(TAG, content)
30 | LOG_E -> Log.e(TAG, content)
31 | else -> {
32 | Log.v(TAG, content)
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/SkinCompatHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget
2 |
3 | /**
4 | * @name SkinCompatHelper
5 | * @package com.lvkang.skin.wedget
6 | * @author 345 QQ:1831712732
7 | * @time 2020/11/29 16:24
8 | * @description
9 | */
10 | abstract class SkinCompatHelper {
11 | companion object {
12 | const val INVALID_ID = 0
13 | }
14 | abstract fun applySkin()
15 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/SkinCompatSupportable.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget
2 |
3 | /**
4 | * @name SkinCompatSupportable
5 | * @package com.lvkang.skin.wedget
6 | * @author 345 QQ:1831712732
7 | * @time 2020/11/29 15:52
8 | * @description
9 | */
10 |
11 | interface SkinCompatSupportable {
12 | fun applySkin()
13 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/android/FrameLayoutX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.android
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.widget.FrameLayout
6 | import androidx.appcompat.widget.LinearLayoutCompat
7 | import androidx.constraintlayout.widget.ConstraintLayout
8 | import com.lvkang.skin.wedget.SkinCompatSupportable
9 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
10 |
11 | /**
12 | * @name LinearLayoutX
13 | * @package com.lvkang.skin.wedget.androidx
14 | * @author 345 QQ:1831712732
15 | * @time 2021/07/28 17:04
16 | * @description
17 | */
18 | class FrameLayoutX : FrameLayout, SkinCompatSupportable {
19 |
20 | private val mBackgroundHelper by lazy {
21 | SkinCompatBackgroundHelper(this)
22 | }
23 |
24 | constructor(context: Context) : this(context, null)
25 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
26 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
27 | context,
28 | attrs,
29 | defStyleAttr
30 | ) {
31 | mBackgroundHelper.loadFromAttributes(attrs, defStyleAttr)
32 | }
33 |
34 | override fun applySkin() {
35 | mBackgroundHelper.applySkin()
36 | }
37 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/android/RelativeLayoutX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.android
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.widget.FrameLayout
6 | import android.widget.RelativeLayout
7 | import androidx.appcompat.widget.LinearLayoutCompat
8 | import androidx.constraintlayout.widget.ConstraintLayout
9 | import com.lvkang.skin.wedget.SkinCompatSupportable
10 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
11 |
12 | /**
13 | * @name LinearLayoutX
14 | * @package com.lvkang.skin.wedget.androidx
15 | * @author 345 QQ:1831712732
16 | * @time 2021/07/28 17:04
17 | * @description
18 | */
19 | class RelativeLayoutX : RelativeLayout, SkinCompatSupportable {
20 |
21 | private val mBackgroundHelper by lazy {
22 | SkinCompatBackgroundHelper(this)
23 | }
24 |
25 | constructor(context: Context) : this(context, null)
26 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
27 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
28 | context,
29 | attrs,
30 | defStyleAttr
31 | ) {
32 | mBackgroundHelper.loadFromAttributes(attrs, defStyleAttr)
33 | }
34 |
35 | override fun applySkin() {
36 | mBackgroundHelper.applySkin()
37 | }
38 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/android/ViewX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.android
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
7 | import com.lvkang.skin.wedget.SkinCompatSupportable
8 |
9 | /**
10 | * @name SkinSupportView
11 | * @package com.lvkang.skin.wedget.support
12 | * @author 345 QQ:1831712732
13 | * @time 2020/12/01 23:34
14 | * @description
15 | */
16 | class ViewX : View, SkinCompatSupportable {
17 |
18 | private val mBackgroundHelper by lazy {
19 | SkinCompatBackgroundHelper(this)
20 | }
21 |
22 | constructor(context: Context?) : this(context, null)
23 | constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
24 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
25 | context, attrs, defStyleAttr
26 | ) {
27 | mBackgroundHelper.loadFromAttributes(attrs, defStyleAttr)
28 | }
29 |
30 | override fun applySkin() {
31 | mBackgroundHelper.applySkin()
32 | }
33 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/androidx/ButtonX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.androidx
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.appcompat.widget.AppCompatButton
6 | import com.lvkang.skin.wedget.SkinCompatSupportable
7 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
8 | import com.lvkang.skin.wedget.helper.SkinCompatTextHelper
9 |
10 | /**
11 | * @name AppxButton
12 | * @package com.lvkang.skin.wedget.androidx
13 | * @author 345 QQ:1831712732
14 | * @time 2020/12/10 23:30
15 | * @description
16 | */
17 | class ButtonX : AppCompatButton, SkinCompatSupportable {
18 |
19 | private val skinTextHelper by lazy(LazyThreadSafetyMode.NONE) { SkinCompatTextHelper(this) }
20 | private val mBackgroundHelper by lazy { SkinCompatBackgroundHelper(this) }
21 |
22 | constructor(context: Context) : this(context, null)
23 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
24 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
25 | context,
26 | attrs,
27 | defStyleAttr
28 | ) {
29 | skinTextHelper.loadFromAttributes(attrs, defStyleAttr)
30 | mBackgroundHelper.loadFromAttributes(attrs, defStyleAttr)
31 | }
32 |
33 | override fun applySkin() {
34 | skinTextHelper.applySkin()
35 | mBackgroundHelper.applySkin()
36 | }
37 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/androidx/ConstraintLayoutX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.androidx
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.constraintlayout.widget.ConstraintLayout
6 | import com.lvkang.skin.wedget.SkinCompatSupportable
7 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
8 |
9 | /**
10 | * @name ConstraintLayoutX
11 | * @package com.lvkang.skin.wedget.androidx
12 | * @author 345 QQ:1831712732
13 | * @time 2021/07/28 16:59
14 | * @description
15 | */
16 | class ConstraintLayoutX : ConstraintLayout, SkinCompatSupportable {
17 |
18 | private val mBackgroundHelper by lazy {
19 | SkinCompatBackgroundHelper(this)
20 | }
21 |
22 | constructor(context: Context) : this(context, null)
23 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
24 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
25 | context,
26 | attrs,
27 | defStyleAttr
28 | ) {
29 | mBackgroundHelper.loadFromAttributes(attrs, defStyleAttr)
30 | }
31 |
32 | override fun applySkin() {
33 | mBackgroundHelper.applySkin()
34 | }
35 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/androidx/EditTextX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.androidx
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.Drawable
5 | import android.util.AttributeSet
6 | import androidx.appcompat.widget.AppCompatEditText
7 | import com.lvkang.skin.wedget.SkinCompatSupportable
8 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
9 | import com.lvkang.skin.wedget.helper.SkinCompatEditTextHelpter
10 |
11 | /**
12 | * @name EditText
13 | * @package com.lvkang.skin.wedget.androidx
14 | * @author 345 QQ:1831712732
15 | * @time 2021/08/02 16:47
16 | * @description
17 | */
18 | class EditTextX : AppCompatEditText, SkinCompatSupportable {
19 |
20 | private val skinTextHelper by lazy(LazyThreadSafetyMode.NONE) { SkinCompatEditTextHelpter(this) }
21 | private val mBackgroundHelper by lazy { SkinCompatBackgroundHelper(this) }
22 |
23 | constructor(context: Context) : this(context, null)
24 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
25 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
26 | context,
27 | attrs,
28 | defStyleAttr
29 | ) {
30 | skinTextHelper.loadFromAttributes(attrs, defStyleAttr)
31 | mBackgroundHelper.loadFromAttributes(attrs, defStyleAttr)
32 | }
33 |
34 | override fun applySkin() {
35 | skinTextHelper.applySkin()
36 | mBackgroundHelper.applySkin()
37 | }
38 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/androidx/ImageViewX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.androidx
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.appcompat.widget.AppCompatImageView
6 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
7 | import com.lvkang.skin.wedget.SkinCompatSupportable
8 | import com.lvkang.skin.wedget.helper.SkinCompatImageHelper
9 |
10 | /**
11 | * @name AppXImageView
12 | * @package com.lvkang.skin.wedget.androidx
13 | * @author 345 QQ:1831712732
14 | * @time 2020/12/10 22:45
15 | * @description
16 | */
17 | class ImageViewX : AppCompatImageView, SkinCompatSupportable {
18 |
19 |
20 | private val mImageHelper by lazy { SkinCompatImageHelper(this) }
21 | private val backgroundHelper by lazy { SkinCompatBackgroundHelper(this) }
22 |
23 | constructor(context: Context) : this(context, null)
24 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
25 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
26 | context,
27 | attrs,
28 | defStyleAttr
29 | ) {
30 | mImageHelper.loadFromAttributes(attrs, defStyleAttr)
31 | backgroundHelper.loadFromAttributes(attrs, defStyleAttr)
32 | }
33 |
34 | override fun applySkin() {
35 | mImageHelper.applySkin()
36 | backgroundHelper.applySkin()
37 | }
38 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/androidx/LinearLayoutX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.androidx
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.appcompat.widget.LinearLayoutCompat
6 | import com.lvkang.skin.wedget.SkinCompatSupportable
7 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
8 |
9 | /**
10 | * @name LinearLayoutX
11 | * @package com.lvkang.skin.wedget.androidx
12 | * @author 345 QQ:1831712732
13 | * @time 2021/07/28 17:04
14 | * @description
15 | */
16 | class LinearLayoutX : LinearLayoutCompat, SkinCompatSupportable {
17 |
18 | private val mBackgroundHelper by lazy {
19 | SkinCompatBackgroundHelper(this)
20 | }
21 |
22 | constructor(context: Context) : this(context, null)
23 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
24 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
25 | context,
26 | attrs,
27 | defStyleAttr
28 | ) {
29 | mBackgroundHelper.loadFromAttributes(attrs, defStyleAttr)
30 | }
31 |
32 | override fun applySkin() {
33 | mBackgroundHelper.applySkin()
34 | }
35 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/androidx/NestedScrollViewX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.androidx
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.widget.ScrollView
6 | import androidx.core.widget.NestedScrollView
7 | import com.lvkang.skin.wedget.SkinCompatSupportable
8 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
9 |
10 | /**
11 | * @name NestScrollViewX
12 | * @package com.lvkang.skin.wedget.androidx
13 | * @author 345 QQ:1831712732
14 | * @time 2021/07/28 17:20
15 | * @description
16 | */
17 | class NestedScrollViewX : NestedScrollView, SkinCompatSupportable {
18 |
19 | private val mBackgroundHelper by lazy {
20 | SkinCompatBackgroundHelper(this)
21 | }
22 |
23 | constructor(context: Context) : this(context, null)
24 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
25 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
26 | context,
27 | attrs,
28 | defStyleAttr
29 | ) {
30 | mBackgroundHelper.loadFromAttributes(attrs, defStyleAttr)
31 | }
32 |
33 | override fun applySkin() {
34 | mBackgroundHelper.applySkin()
35 | }
36 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/androidx/TextViewX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.androidx
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.appcompat.widget.AppCompatButton
6 | import androidx.appcompat.widget.AppCompatTextView
7 | import com.lvkang.skin.wedget.SkinCompatSupportable
8 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
9 | import com.lvkang.skin.wedget.helper.SkinCompatTextHelper
10 |
11 | /**
12 | * @name AppxButton
13 | * @package com.lvkang.skin.wedget.androidx
14 | * @author 345 QQ:1831712732
15 | * @time 2020/12/10 23:30
16 | * @description
17 | */
18 | class TextViewX : AppCompatTextView, SkinCompatSupportable {
19 |
20 | private val skinTextHelper by lazy(LazyThreadSafetyMode.NONE) { SkinCompatTextHelper(this) }
21 | private val mBackgroundHelper by lazy { SkinCompatBackgroundHelper(this) }
22 |
23 | constructor(context: Context) : this(context, null)
24 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
25 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
26 | context,
27 | attrs,
28 | defStyleAttr
29 | ) {
30 | skinTextHelper.loadFromAttributes(attrs, defStyleAttr)
31 | mBackgroundHelper.loadFromAttributes(attrs, defStyleAttr)
32 | }
33 |
34 | override fun applySkin() {
35 | skinTextHelper.applySkin()
36 | mBackgroundHelper.applySkin()
37 | }
38 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/androidx/cardview/CardViewX.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.androidx.cardview
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.appcompat.widget.AppCompatButton
6 | import androidx.cardview.widget.CardView
7 | import com.lvkang.skin.wedget.SkinCompatSupportable
8 | import com.lvkang.skin.wedget.helper.SkinCompatBackgroundHelper
9 | import com.lvkang.skin.wedget.helper.SkinCompatCardHelper
10 | import com.lvkang.skin.wedget.helper.SkinCompatTextHelper
11 |
12 | /**
13 | * @name CardViewX
14 | * @package com.lvkang.skin.wedget.androidx
15 | * @author 345 QQ:1831712732
16 | * @time 2021/08/03 21:57
17 | * @description
18 | */
19 | internal class CardViewX : CardView, SkinCompatSupportable {
20 |
21 | private val skinCompatCardHelper by lazy { SkinCompatCardHelper(this) }
22 |
23 | constructor(context: Context) : this(context, null)
24 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
25 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
26 | context,
27 | attrs,
28 | defStyleAttr
29 | ) {
30 | skinCompatCardHelper.loadFromAttributes(attrs, defStyleAttr)
31 | }
32 |
33 | override fun applySkin() {
34 | skinCompatCardHelper.applySkin()
35 | }
36 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/androidx/cardview/RoundRectDrawable.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.androidx.cardview
2 |
3 | import android.content.res.ColorStateList
4 | import android.graphics.*
5 | import android.graphics.drawable.Drawable
6 | import androidx.annotation.RequiresApi
7 | import kotlin.math.cos
8 |
9 | /**
10 | * @name RoundRectDrawable
11 | * @package com.lvkang.skin.wedget.androidx.cardview
12 | * @author 345 QQ:1831712732
13 | * @time 2021/08/04 11:21
14 | * @description
15 | */
16 | /**
17 | * Very simple drawable that draws a rounded rectangle background with arbitrary corners and also
18 | * reports proper outline for Lollipop.
19 | *
20 | *
21 | * Simpler and uses less resources compared to GradientDrawable or ShapeDrawable.
22 | */
23 | @RequiresApi(21)
24 | internal class RoundRectDrawable(backgroundColor: ColorStateList?, private var mRadius: Float) :
25 | Drawable() {
26 | private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG)
27 | private val mBoundsF: RectF
28 | private val mBoundsI: Rect
29 | var padding = 0f
30 | private set
31 | private var mInsetForPadding = false
32 | private var mInsetForRadius = true
33 | private var mBackground: ColorStateList? = null
34 | private var mTintFilter: PorterDuffColorFilter? = null
35 | private var mTint: ColorStateList? = null
36 | private var mTintMode: PorterDuff.Mode? = PorterDuff.Mode.SRC_IN
37 |
38 |
39 | companion object {
40 | const val SHADOW_MULTIPLIER = 1.5f
41 | val COS_45 = cos(Math.toRadians(45.0))
42 | }
43 |
44 |
45 | private fun setBackground(color: ColorStateList?) {
46 | mBackground = color ?: ColorStateList.valueOf(Color.TRANSPARENT)
47 | mPaint.color = mBackground!!.getColorForState(state, mBackground!!.defaultColor)
48 | }
49 |
50 | fun setPadding(padding: Float, insetForPadding: Boolean, insetForRadius: Boolean) {
51 | if (padding == this.padding && mInsetForPadding == insetForPadding && mInsetForRadius == insetForRadius) {
52 | return
53 | }
54 | this.padding = padding
55 | mInsetForPadding = insetForPadding
56 | mInsetForRadius = insetForRadius
57 | updateBounds(null)
58 | invalidateSelf()
59 | }
60 |
61 | override fun draw(canvas: Canvas) {
62 | val paint = mPaint
63 | val clearColorFilter: Boolean
64 | if (mTintFilter != null && paint.colorFilter == null) {
65 | paint.colorFilter = mTintFilter
66 | clearColorFilter = true
67 | } else {
68 | clearColorFilter = false
69 | }
70 | canvas.drawRoundRect(mBoundsF, mRadius, mRadius, paint)
71 | if (clearColorFilter) {
72 | paint.colorFilter = null
73 | }
74 | }
75 |
76 | private fun updateBounds(bounds: Rect?) {
77 | var bounds = bounds
78 | if (bounds == null) {
79 | bounds = getBounds()
80 | }
81 | mBoundsF[bounds.left.toFloat(), bounds.top.toFloat(), bounds.right.toFloat()] =
82 | bounds.bottom.toFloat()
83 | mBoundsI.set(bounds)
84 | if (mInsetForPadding) {
85 | val vInset = calculateVerticalPadding(
86 | padding,
87 | mRadius,
88 | mInsetForRadius
89 | )
90 | val hInset = calculateHorizontalPadding(
91 | padding,
92 | mRadius,
93 | mInsetForRadius
94 | )
95 | mBoundsI.inset(
96 | Math.ceil(hInset.toDouble()).toInt(),
97 | Math.ceil(vInset.toDouble()).toInt()
98 | )
99 | // to make sure they have same bounds.
100 | mBoundsF.set(mBoundsI)
101 | }
102 | }
103 |
104 | override fun onBoundsChange(bounds: Rect) {
105 | super.onBoundsChange(bounds)
106 | updateBounds(bounds)
107 | }
108 |
109 | override fun getOutline(outline: Outline) {
110 | outline.setRoundRect(mBoundsI, mRadius)
111 | }
112 |
113 | override fun setAlpha(alpha: Int) {
114 | mPaint.alpha = alpha
115 | }
116 |
117 | override fun setColorFilter(cf: ColorFilter?) {
118 | mPaint.colorFilter = cf
119 | }
120 |
121 | override fun getOpacity(): Int {
122 | return PixelFormat.TRANSLUCENT
123 | }
124 |
125 | var radius: Float
126 | get() = mRadius
127 | set(radius) {
128 | if (radius == mRadius) {
129 | return
130 | }
131 | mRadius = radius
132 | updateBounds(null)
133 | invalidateSelf()
134 | }
135 | var color: ColorStateList?
136 | get() = mBackground
137 | set(color) {
138 | setBackground(color)
139 | invalidateSelf()
140 | }
141 |
142 | override fun setTintList(tint: ColorStateList?) {
143 | mTint = tint
144 | mTintFilter = createTintFilter(mTint, mTintMode)
145 | invalidateSelf()
146 | }
147 |
148 | override fun setTintMode(tintMode: PorterDuff.Mode?) {
149 | mTintMode = tintMode
150 | mTintFilter = createTintFilter(mTint, mTintMode)
151 | invalidateSelf()
152 | }
153 |
154 | override fun onStateChange(stateSet: IntArray): Boolean {
155 | val newColor = mBackground!!.getColorForState(stateSet, mBackground!!.defaultColor)
156 | val colorChanged = newColor != mPaint.color
157 | if (colorChanged) {
158 | mPaint.color = newColor
159 | }
160 | if (mTint != null && mTintMode != null) {
161 | mTintFilter = createTintFilter(mTint, mTintMode)
162 | return true
163 | }
164 | return colorChanged
165 | }
166 |
167 | override fun isStateful(): Boolean {
168 | return (mTint != null && mTint!!.isStateful
169 | || mBackground != null && mBackground!!.isStateful || super.isStateful())
170 | }
171 |
172 | /**
173 | * Ensures the tint filter is consistent with the current tint color and
174 | * mode.
175 | */
176 | private fun createTintFilter(
177 | tint: ColorStateList?,
178 | tintMode: PorterDuff.Mode?
179 | ): PorterDuffColorFilter? {
180 | if (tint == null || tintMode == null) {
181 | return null
182 | }
183 | val color = tint.getColorForState(state, Color.TRANSPARENT)
184 | return PorterDuffColorFilter(color, tintMode)
185 | }
186 |
187 | init {
188 | setBackground(backgroundColor)
189 | mBoundsF = RectF()
190 | mBoundsI = Rect()
191 | }
192 |
193 |
194 | private fun calculateVerticalPadding(
195 | maxShadowSize: Float, cornerRadius: Float,
196 | addPaddingForCorners: Boolean
197 | ): Float {
198 | return if (addPaddingForCorners) {
199 | (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius).toFloat()
200 | } else {
201 | maxShadowSize * SHADOW_MULTIPLIER
202 | }
203 | }
204 |
205 | private fun calculateHorizontalPadding(
206 | maxShadowSize: Float, cornerRadius: Float, addPaddingForCorners: Boolean
207 | ): Float {
208 | return if (addPaddingForCorners) {
209 | (maxShadowSize + (1 - COS_45) * cornerRadius).toFloat()
210 | } else {
211 | maxShadowSize
212 | }
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/helper/SkinCompatBackgroundHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.helper
2 |
3 | import android.util.AttributeSet
4 | import android.util.Log
5 | import android.view.View
6 | import androidx.core.view.ViewCompat
7 | import androidx.appcompat.R
8 | import com.lvkang.skin.ktx.obtainStyledAttributes
9 | import com.lvkang.skin.resource.SkinCompatResources
10 | import com.lvkang.skin.wedget.SkinCompatHelper
11 |
12 | /**
13 | * @name SkinCompatBackgroundHelper
14 | * @package com.lvkang.skin.wedget
15 | * @author 345 QQ:1831712732
16 | * @time 2020/11/29 16:30
17 | * @description 更换背景的帮助类
18 | */
19 |
20 | class SkinCompatBackgroundHelper(val view: View) : SkinCompatHelper() {
21 |
22 | private var backgroundResId = INVALID_ID
23 |
24 | fun loadFromAttributes(attrs: AttributeSet?, defStyleAttr: Int) {
25 | obtainStyledAttributes(
26 | view, attrs, R.styleable.ViewBackgroundHelper, defStyleAttr, 0
27 | ) {
28 | backgroundResId = it.getResourceId(
29 | R.styleable.ViewBackgroundHelper_android_background, INVALID_ID
30 | )
31 | }
32 | applySkin()
33 | }
34 |
35 |
36 | override fun applySkin() {
37 | setBackground(backgroundResId)
38 | }
39 |
40 | private fun setBackground(res: Int) {
41 | if (res == INVALID_ID) return
42 | val drawable = SkinCompatResources.getDrawable(res)
43 | if (drawable != null) {
44 | val paddingleft = view.paddingLeft
45 | val paddingTop = view.paddingTop
46 | val paddingRight = view.paddingRight
47 | val paddingBottom = view.paddingBottom
48 | ViewCompat.setBackground(view, drawable)
49 | view.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom)
50 | return
51 | }
52 | val color = SkinCompatResources.getColor(res)
53 | color?.run {
54 | view.setBackgroundColor(color)
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/helper/SkinCompatCardHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.helper
2 |
3 | import android.util.AttributeSet
4 | import androidx.cardview.widget.CardView
5 | import com.lvkang.skin.ktx.obtainStyledAttributes
6 | import com.lvkang.skin.resource.SkinCompatResources
7 | import com.lvkang.skin.wedget.SkinCompatHelper
8 |
9 | import android.content.res.ColorStateList
10 | import android.content.res.TypedArray
11 | import android.graphics.Color
12 | import androidx.core.content.res.ResourcesCompat
13 | import com.lvkang.skin.R
14 | import com.lvkang.skin.util.SkinLog
15 | import com.lvkang.skin.wedget.androidx.cardview.RoundRectDrawable
16 |
17 |
18 | /**
19 | * @name SkinCompatCardHelper
20 | * @package com.lvkang.skin.wedget.helper
21 | * @author 345 QQ:1831712732
22 | * @time 2021/08/04 10:42
23 | * @description
24 | */
25 | class SkinCompatCardHelper(private val view: CardView) : SkinCompatHelper() {
26 |
27 | private var appBackgroundResId = INVALID_ID
28 | private var appCardCornerRadiusResId = INVALID_ID
29 | private var appCardElevationResId = INVALID_ID
30 |
31 |
32 | private var radius = 0f
33 | private var colorStateList: ColorStateList? = null
34 |
35 |
36 | fun loadFromAttributes(attrs: AttributeSet?, defStyleAttr: Int) {
37 | obtainStyledAttributes(
38 | view, attrs, R.styleable.CardView, defStyleAttr, R.style.CardView
39 | ) {
40 | appBackgroundResId = it.getResourceId(
41 | R.styleable.CardView_cardBackgroundColor, INVALID_ID
42 | )
43 | appCardCornerRadiusResId = it.getResourceId(
44 | R.styleable.CardView_cardCornerRadius, INVALID_ID
45 | )
46 | appCardElevationResId = it.getResourceId(
47 | R.styleable.CardView_cardElevation, INVALID_ID
48 | )
49 | //缓存默认设置的圆角
50 | colorStateList =
51 | it.getColorStateList(R.styleable.CardView_cardBackgroundColor)
52 | SkinLog.log(colorStateList.toString())
53 | radius = it.getDimension(R.styleable.CardView_cardElevation, 0f)
54 | }
55 | applySkin()
56 | }
57 |
58 | override fun applySkin() {
59 | setElevation(appCardElevationResId)
60 | setBackgroundAndRadius(appBackgroundResId, appCardCornerRadiusResId)
61 | }
62 |
63 | private fun setElevation(resId: Int) {
64 | if (resId == INVALID_ID) return
65 | val elevation = SkinCompatResources.getDimension(resId) ?: 0f
66 | view.clipToOutline = true
67 | view.elevation = elevation
68 | }
69 |
70 | private fun setBackgroundAndRadius(
71 | bgRes: Int,
72 | radiusRes: Int
73 | ) {
74 | //如果没有使用皮肤
75 | if (bgRes == INVALID_ID) {
76 | //并且没有缓存,则根据主题获取背景等属性
77 | if (colorStateList == null) {
78 | // There isn't one set, so we'll compute one based on the theme
79 | val aa: TypedArray =
80 | view.context.obtainStyledAttributes(intArrayOf(android.R.attr.colorBackground))
81 | val themeColorBackground = aa.getColor(0, 0)
82 | aa.recycle()
83 | // If the theme colorBackground is light, use our own light color, otherwise dark
84 | val hsv = FloatArray(3)
85 | Color.colorToHSV(themeColorBackground, hsv)
86 | colorStateList = ColorStateList.valueOf(
87 | if (hsv[2] > 0.5f)
88 | ResourcesCompat.getColor(
89 | view.resources, R.color.cardview_light_background, null
90 | )
91 | else
92 | ResourcesCompat.getColor(
93 | view.resources, R.color.cardview_dark_background, null
94 | )
95 | )
96 | }
97 | } else {
98 | SkinCompatResources.getColorStateList(bgRes)?.run {
99 | colorStateList = this
100 | }
101 | }
102 | if (radiusRes != INVALID_ID)
103 | SkinCompatResources.getDimension(radiusRes)?.run {
104 | radius = this
105 | }
106 | if (colorStateList != null) {
107 | val drawable = RoundRectDrawable(colorStateList, radius)
108 | view.background = drawable
109 | }
110 | }
111 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/helper/SkinCompatEditTextHelpter.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.helper
2 |
3 | import android.util.AttributeSet
4 | import android.widget.EditText
5 | import com.lvkang.skin.R
6 | import com.lvkang.skin.ktx.obtainStyledAttributes
7 | import com.lvkang.skin.resource.SkinCompatResources
8 |
9 | /**
10 | * @name SkinCompatEditTextHelpter
11 | * @package com.lvkang.skin.wedget.helper
12 | * @author 345 QQ:1831712732
13 | * @time 2021/08/02 16:51
14 | * @description
15 | */
16 | class SkinCompatEditTextHelpter(private val edit: EditText) : SkinCompatTextHelper(edit) {
17 | private var hintId = INVALID_ID
18 | override fun loadFromAttributes(attrs: AttributeSet?, defStyleAttr: Int) {
19 | obtainStyledAttributes(
20 | edit, attrs, R.styleable.skinTextHelper, defStyleAttr, 0
21 | ) {
22 | sizeId = it.getResourceId(R.styleable.skinTextHelper_android_textSize, INVALID_ID)
23 | textColorId = it.getResourceId(R.styleable.skinTextHelper_android_textColor, INVALID_ID)
24 | textId = it.getResourceId(R.styleable.skinTextHelper_android_text, INVALID_ID)
25 | hintId = it.getResourceId(R.styleable.skinTextHelper_android_hint, INVALID_ID)
26 | }
27 | applySkin()
28 | }
29 |
30 | override fun applySkin() {
31 | super.applySkin()
32 | setHintText(hintId)
33 | }
34 |
35 | private fun setHintText(res: Int) {
36 | if (res == INVALID_ID) return
37 | val string = SkinCompatResources.getString(res)
38 | string?.run { edit.hint = this }
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/helper/SkinCompatImageHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.helper
2 |
3 | import android.util.AttributeSet
4 | import android.util.Log
5 | import android.widget.ImageView
6 | import androidx.appcompat.content.res.AppCompatResources
7 | import androidx.appcompat.widget.AppCompatImageView
8 | import androidx.core.view.ViewCompat
9 | import androidx.appcompat.R
10 | import com.lvkang.skin.ktx.obtainStyledAttributes
11 | import com.lvkang.skin.resource.SkinCompatResources
12 | import com.lvkang.skin.wedget.SkinCompatHelper
13 |
14 | /**
15 | * @name SkinCompatImageHelper
16 | * @package com.lvkang.skin.wedget.helper
17 | * @author 345 QQ:1831712732
18 | * @time 2020/12/10 22:56
19 | * @description
20 | */
21 | class SkinCompatImageHelper(val view: AppCompatImageView) : SkinCompatHelper() {
22 |
23 | var src = INVALID_ID
24 |
25 | fun loadFromAttributes(attrs: AttributeSet?, defStyleAttr: Int) {
26 | obtainStyledAttributes(
27 | view, attrs, R.styleable.AppCompatImageView, defStyleAttr, 0
28 | ) {
29 | src = it.getResourceId(
30 | R.styleable.AppCompatImageView_android_src, INVALID_ID
31 | )
32 | }
33 | applySkin()
34 | }
35 |
36 | override fun applySkin() {
37 | setImage(src)
38 | }
39 |
40 | private fun setImage(res: Int) {
41 | if (res == INVALID_ID) return
42 | val drawable = SkinCompatResources.getDrawable(res)
43 | if (drawable != null) {
44 | val paddingleft = view.paddingLeft
45 | val paddingTop = view.paddingTop
46 | val paddingRight = view.paddingRight
47 | val paddingBottom = view.paddingBottom
48 | view.setImageDrawable(drawable)
49 | view.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom)
50 | return
51 | }
52 | val color = SkinCompatResources.getColor(res)
53 | color?.run {
54 | view.setBackgroundColor(color)
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/skin/src/main/java/com/lvkang/skin/wedget/helper/SkinCompatTextHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin.wedget.helper
2 |
3 | import android.util.AttributeSet
4 | import android.widget.TextView
5 | import com.lvkang.skin.R
6 | import com.lvkang.skin.ktx.obtainStyledAttributes
7 | import com.lvkang.skin.ktx.px2dip
8 | import com.lvkang.skin.resource.SkinCompatResources
9 | import com.lvkang.skin.util.SkinLog
10 | import com.lvkang.skin.wedget.SkinCompatHelper
11 |
12 | /**
13 | * @name SkinCompatImageHelper
14 | * @package com.lvkang.skin.wedget.helper
15 | * @author 345 QQ:1831712732
16 | * @time 2020/12/10 23:10
17 | * @description
18 | */
19 | open class SkinCompatTextHelper(private val view: TextView) : SkinCompatHelper() {
20 |
21 | internal var sizeId = INVALID_ID
22 | internal var textColorId = INVALID_ID
23 | internal var textId = INVALID_ID
24 |
25 | open fun loadFromAttributes(attrs: AttributeSet?, defStyleAttr: Int) {
26 | obtainStyledAttributes(
27 | view, attrs, R.styleable.skinTextHelper, defStyleAttr, 0
28 | ) {
29 | sizeId = it.getResourceId(R.styleable.skinTextHelper_android_textSize, INVALID_ID)
30 | textColorId = it.getResourceId(R.styleable.skinTextHelper_android_textColor, INVALID_ID)
31 | textId = it.getResourceId(R.styleable.skinTextHelper_android_text, INVALID_ID)
32 | }
33 | applySkin()
34 | }
35 |
36 |
37 | override fun applySkin() {
38 | setSize(sizeId)
39 | setText(textId)
40 | setTextColor(textColorId)
41 | }
42 |
43 | private fun setText(res: Int) {
44 | if (res == INVALID_ID) return
45 | val string = SkinCompatResources.getString(res)
46 | string?.run { view.text = string }
47 | }
48 |
49 | private fun setTextColor(res: Int) {
50 | if (res == INVALID_ID) return
51 | val color = SkinCompatResources.getColor(res)
52 | color?.run { view.setTextColor(this) }
53 | }
54 |
55 | private fun setSize(res: Int) {
56 | if (res == INVALID_ID) return
57 | val size = SkinCompatResources.getDimension(res)
58 | size?.run { view.textSize = px2dip(this) }
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/skin/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/skin/src/test/java/com/lvkang/skin/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.lvkang.skin
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------