├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── wanjian │ │ └── shadow │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── wanjian │ │ │ └── shadow │ │ │ ├── MainActivity.java │ │ │ └── RadiusView.java │ └── res │ │ ├── layout │ │ └── activity_main.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 │ │ ├── erha.jpg │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── wanjian │ └── shadow │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── shadowlib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── wanjian │ └── shadowlib │ └── ExampleInstrumentedTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── wanjian │ │ └── shadowlib │ │ ├── Check.java │ │ ├── Config.java │ │ └── ShadowHelper.java └── res │ └── values │ └── strings.xml └── test └── java └── com └── wanjian └── shadowlib └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .idea 11 | 12 | # Created by .ignore support plugin (hsz.mobi) 13 | ### macOS template 14 | *.DS_Store 15 | .AppleDouble 16 | .LSOverride 17 | 18 | # Icon must end with two \r 19 | Icon 20 | 21 | 22 | # Thumbnails 23 | ._* 24 | 25 | # Files that might appear in the root of a volume 26 | .DocumentRevisions-V100 27 | .fseventsd 28 | .Spotlight-V100 29 | .TemporaryItems 30 | .Trashes 31 | .VolumeIcon.icns 32 | .com.apple.timemachine.donotpresent 33 | 34 | # Directories potentially created on remote AFP share 35 | .AppleDB 36 | .AppleDesktop 37 | Network Trash Folder 38 | Temporary Items 39 | .apdisk 40 | ### Android template 41 | # Built application files 42 | *.apk 43 | *.ap_ 44 | 45 | # Files for the ART/Dalvik VM 46 | *.dex 47 | 48 | # Java class files 49 | *.class 50 | 51 | # Generated files 52 | bin/ 53 | gen/ 54 | out/ 55 | 56 | # Gradle files 57 | .gradle/ 58 | build/ 59 | 60 | # Local configuration file (sdk path, etc) 61 | local.properties 62 | 63 | # Proguard folder generated by Eclipse 64 | proguard/ 65 | 66 | # Log Files 67 | *.log 68 | 69 | # Android Studio Navigation editor temp files 70 | .navigation/ 71 | 72 | # Android Studio captures folder 73 | captures/ 74 | 75 | # Intellij 76 | *.iml 77 | .idea/workspace.xml 78 | .idea/tasks.xml 79 | .idea/gradle.xml 80 | .idea/dictionaries 81 | .idea/libraries 82 | 83 | # Keystore files 84 | *.jks 85 | 86 | # External native build folder generated in Android Studio 2.2 and later 87 | .externalNativeBuild 88 | 89 | # Google Services (e.g. APIs or Firebase) 90 | google-services.json 91 | 92 | # Freeline 93 | freeline.py 94 | freeline/ 95 | freeline_project_description.json 96 | ### Linux template 97 | *~ 98 | 99 | # temporary files which can be created if a process still has a handle open of a deleted file 100 | .fuse_hidden* 101 | 102 | # KDE directory preferences 103 | .directory 104 | 105 | # Linux trash folder which might appear on any partition or disk 106 | .Trash-* 107 | 108 | # .nfs files are created when an open file is removed but is still being accessed 109 | .nfs* 110 | ### Windows template 111 | # Windows thumbnail cache files 112 | Thumbs.db 113 | ehthumbs.db 114 | ehthumbs_vista.db 115 | 116 | # Folder config file 117 | Desktop.ini 118 | 119 | # Recycle Bin used on file shares 120 | $RECYCLE.BIN/ 121 | 122 | # Windows Installer files 123 | *.cab 124 | *.msi 125 | *.msm 126 | *.msp 127 | 128 | # Windows shortcuts 129 | *.lnk 130 | ### JetBrains template 131 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 132 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 133 | 134 | # User-specific stuff: 135 | .idea/**/workspace.xml 136 | .idea/**/tasks.xml 137 | .idea/dictionaries 138 | 139 | # Sensitive or high-churn files: 140 | .idea/**/dataSources/ 141 | .idea/**/dataSources.ids 142 | .idea/**/dataSources.xml 143 | .idea/**/dataSources.local.xml 144 | .idea/**/sqlDataSources.xml 145 | .idea/**/dynamic.xml 146 | .idea/**/uiDesigner.xml 147 | 148 | # Gradle: 149 | .idea/**/gradle.xml 150 | .idea/**/libraries 151 | 152 | # Mongo Explorer plugin: 153 | .idea/**/mongoSettings.xml 154 | 155 | ## File-based project format: 156 | *.iws 157 | 158 | ## Plugin-specific files: 159 | 160 | # IntelliJ 161 | /out/ 162 | 163 | # mpeltonen/sbt-idea plugin 164 | .idea_modules/ 165 | 166 | # JIRA plugin 167 | atlassian-ide-plugin.xml 168 | 169 | # Crashlytics plugin (for Android Studio and IntelliJ) 170 | com_crashlytics_export_strings.xml 171 | crashlytics.properties 172 | crashlytics-build.properties 173 | fabric.properties 174 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 wanjian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Android实现和web中一样的阴影效果 2 | 3 | 阴影是画在view外面的,不是画在view上的 4 | 5 | ##### 使用方式: 6 | 自定义view,并在onDraw中执行如下代码 7 | 8 | ```java 9 | 10 | ShadowHelper.draw(canvas, this, 11 | Config.obtain() 12 | .color(color) 13 | .leftTopCorner((int) lt) 14 | .rightTopCorner((int) rt) 15 | .leftBottomCorner((int) lb) 16 | .rightBottomCorner((int) rb) 17 | .radius(radius) 18 | .xOffset(xOffset) 19 | .yOffset(yOffset) 20 | ); 21 | 22 | ``` 23 | 24 | 具体用法参考 `RadiusView.java` 25 | 26 | ##### 效果 27 | 28 | ![1](https://raw.githubusercontent.com/android-notes/blogimg/master/shadow1.png) 29 | ![1](https://raw.githubusercontent.com/android-notes/blogimg/master/shadow2.png) 30 | ![1](https://raw.githubusercontent.com/android-notes/blogimg/master/shadow3.png) 31 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.3" 6 | defaultConfig { 7 | applicationId "com.wanjian.shadow" 8 | minSdkVersion 14 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.3.1' 28 | compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha9' 29 | testCompile 'junit:junit:4.12' 30 | compile project(':shadowlib') 31 | compile 'com.squareup.okhttp3:okhttp:3.9.0' 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/wanjian/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/wanjian/shadow/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.wanjian.shadow; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.wanjian.shadow", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/wanjian/shadow/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wanjian.shadow; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.widget.EditText; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_main); 15 | 16 | final EditText lt = (EditText) findViewById(R.id.lt); 17 | final EditText rt = (EditText) findViewById(R.id.rt); 18 | final EditText rb = (EditText) findViewById(R.id.rb); 19 | final EditText lb = (EditText) findViewById(R.id.lb); 20 | final EditText x = (EditText) findViewById(R.id.xoffset); 21 | final EditText y = (EditText) findViewById(R.id.yoffset); 22 | final EditText r = (EditText) findViewById(R.id.radius); 23 | final EditText color = (EditText) findViewById(R.id.color); 24 | final RadiusView radiusView = (RadiusView) findViewById(R.id.rv); 25 | 26 | View ok = findViewById(R.id.ok); 27 | ok.setOnClickListener(new View.OnClickListener() { 28 | @Override 29 | public void onClick(View v) { 30 | float ltRadius = parseFloat(lt.getText().toString()); 31 | float rtRadius = parseFloat(rt.getText().toString()); 32 | float rbRadius = parseFloat(rb.getText().toString()); 33 | float lbRadius = parseFloat(lb.getText().toString()); 34 | radiusView.setRadius(ltRadius, rtRadius, rbRadius, lbRadius); 35 | 36 | 37 | int xOffset = parseInt(x.getText().toString()); 38 | int yOffset = parseInt(y.getText().toString()); 39 | int radius = parseInt(r.getText().toString()); 40 | int c = Color.parseColor("#" + color.getText().toString().trim()); 41 | radiusView.setShadow(xOffset, yOffset, radius, c); 42 | } 43 | }); 44 | 45 | View container = findViewById(R.id.container); 46 | container.setOnClickListener(new View.OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | v.setBackgroundColor((int) (Math.random() * Integer.MAX_VALUE)); 50 | } 51 | }); 52 | 53 | ok.performClick(); 54 | 55 | } 56 | 57 | private float parseFloat(String s) { 58 | try { 59 | return Float.parseFloat(s); 60 | } catch (Exception e) { 61 | return 0; 62 | } 63 | } 64 | 65 | private int parseInt(String s) { 66 | try { 67 | return Integer.parseInt(s); 68 | } catch (Exception e) { 69 | return 0; 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/wanjian/shadow/RadiusView.java: -------------------------------------------------------------------------------- 1 | package com.wanjian.shadow; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Matrix; 8 | import android.graphics.Path; 9 | import android.graphics.RectF; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | 13 | import com.wanjian.shadowlib.Config; 14 | import com.wanjian.shadowlib.ShadowHelper; 15 | 16 | /** 17 | * Created by wanjian on 2017/9/15. 18 | *

19 | * 圆角+阴影 Demo 20 | */ 21 | 22 | public class RadiusView extends View { 23 | private float lt, rt, rb, lb, radius; 24 | private int xOffset, yOffset; 25 | private Path path = new Path(); 26 | private int color; 27 | 28 | private Bitmap bitmap; 29 | 30 | public RadiusView(Context context) { 31 | super(context); 32 | init(); 33 | } 34 | 35 | public RadiusView(Context context, AttributeSet attrs) { 36 | super(context, attrs); 37 | init(); 38 | } 39 | 40 | private void init() { 41 | bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.erha); 42 | 43 | setOnClickListener(new OnClickListener() { 44 | @Override 45 | public void onClick(View v) { 46 | invalidate(); 47 | } 48 | }); 49 | } 50 | 51 | 52 | public void setRadius(float lt, float rt, float rb, float lb) { 53 | this.lt = lt; 54 | this.rt = rt; 55 | this.rb = rb; 56 | this.lb = lb; 57 | invalidate(); 58 | } 59 | 60 | public void setShadow(int xoffset, int yoffset, float radius, int color) { 61 | this.xOffset = xoffset; 62 | this.yOffset = yoffset; 63 | this.radius = radius; 64 | this.color = color; 65 | this.radius = radius; 66 | invalidate(); 67 | } 68 | 69 | @Override 70 | protected void onDraw(Canvas canvas) { 71 | super.onDraw(canvas); 72 | drawImage(canvas); 73 | 74 | //实现阴影 75 | ShadowHelper.draw(canvas, this, 76 | Config.obtain() 77 | .color(color) 78 | .leftTopCorner((int) lt) 79 | .rightTopCorner((int) rt) 80 | .leftBottomCorner((int) lb) 81 | .rightBottomCorner((int) rb) 82 | .radius(radius) 83 | .xOffset(xOffset) 84 | .yOffset(yOffset) 85 | ); 86 | } 87 | 88 | private void drawImage(Canvas canvas) { 89 | canvas.save(); 90 | clipBorder(canvas); 91 | 92 | float scale = Math.max(1f * getWidth() / bitmap.getWidth(), 1f * getHeight() / bitmap.getHeight()); 93 | Matrix matrix = new Matrix(); 94 | matrix.setScale(scale, scale); 95 | canvas.drawBitmap(bitmap, matrix, null); 96 | 97 | canvas.restore(); 98 | 99 | } 100 | 101 | private void clipBorder(Canvas canvas) { 102 | int w = getWidth(); 103 | int h = getHeight(); 104 | if (lt + rt > w) { 105 | float scale = w / (lt + rt); 106 | lt *= scale; 107 | rt *= scale; 108 | rb *= scale; 109 | lb *= scale; 110 | } 111 | if (rt + rb > h) { 112 | float scale = h / (rt + rb); 113 | lt *= scale; 114 | rt *= scale; 115 | rb *= scale; 116 | lb *= scale; 117 | } 118 | 119 | if (rb + lb > h) { 120 | float scale = w / (rb + lb); 121 | lt *= scale; 122 | rt *= scale; 123 | rb *= scale; 124 | lb *= scale; 125 | } 126 | 127 | if (lt + lb > h) { 128 | float scale = w / (lt + lb); 129 | lt *= scale; 130 | rt *= scale; 131 | rb *= scale; 132 | lb *= scale; 133 | } 134 | 135 | path.reset(); 136 | path.arcTo(new RectF(0, 0, lt * 2, lt * 2), 180, 90, false); 137 | path.arcTo(new RectF(w - rt * 2, 0, w, rt * 2), -90, 90, false); 138 | path.arcTo(new RectF(w - rb * 2, h - rb * 2, w, h), 0, 90, false); 139 | path.arcTo(new RectF(0, h - lb * 2, lb * 2, h), 90, 90, false); 140 | 141 | path.close(); 142 | try { 143 | canvas.clipPath(path); 144 | } catch (Exception e) { 145 | e.printStackTrace(); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 22 | 23 | 24 | 29 | 30 | 31 | 36 | 37 | 41 | 42 | 50 | 51 | 59 | 60 | 61 | 62 | 66 | 67 | 68 | 76 | 77 | 85 | 86 | 87 | 88 | 89 | 90 | 94 | 95 | 99 | 100 | 108 | 109 | 117 | 118 | 126 | 127 | 135 | 136 | 137 | 138 | 139 |