├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── art
└── screenshot.png
├── build.gradle
├── demo
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── bilibili
│ │ └── draweetext
│ │ └── demo
│ │ ├── MainActivity.java
│ │ └── MainApplication.java
│ └── res
│ ├── layout
│ ├── activity_main.xml
│ ├── fragment_recycler.xml
│ ├── fragment_simple.xml
│ └── simple_text.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── draweetext
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── bilibili
│ │ └── draweetext
│ │ ├── DraweeSpan.java
│ │ └── DraweeTextView.java
│ └── test
│ └── java
│ └── com
│ └── bilibili
│ └── draweetext
│ ├── DraweeSpanTest.java
│ └── DraweeTextViewTest.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk: oraclejdk8
3 | android:
4 | components:
5 | - platform-tools
6 | - tools
7 | - build-tools-25.0.1
8 | - android-25
9 | - extra-android-m2repository
10 |
11 | script:
12 | - ./gradlew clean :draweetext:check --no-daemon --stacktrace
13 |
14 | notifications:
15 | email: false
16 |
17 | sudo: false
18 |
19 | before_cache:
20 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
21 |
22 | cache:
23 | directories:
24 | - $HOME/.gradle
25 | - $HOME/.gradle/caches/
26 | - $HOME/.gradle/wrapper/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DraweeTextView
2 | Simple drawee spannable text view based on [Fresco][1].
3 | [](https://travis-ci.org/Bilibili/drawee-text-view)
4 | # Usage
5 | ```java
6 | DraweeTextView textview = (DraweeTextView)findViewById(R.id.text);
7 |
8 | SpannableStringBuilder builder = new SpannableStringBuilder();
9 | builder.append("2333333\n")
10 | start = builder.length();
11 | builder.append("[emotion:tv_cheers]");
12 | DraweeSpan span = new DraweeSpan("http://static.yo9.com/web/emotions/tv_cheers.png");
13 | builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14 | builder.append("bilibili- ( ゜- ゜)つロ 乾杯~\n");
15 |
16 | ...
17 |
18 | textview.setText(builder);
19 | ```
20 | 
21 |
22 |
23 | [1]: https://github.com/facebook/fresco
24 |
--------------------------------------------------------------------------------
/art/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/drawee-text-view/7126d19fc4cbcf5160ef29a5f828d23a0e7611dd/art/screenshot.png
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.0.0'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
27 |
28 | ext {
29 | frescoVersion = '1.5.0'
30 | supportLibraryVersion = '26.0.2'
31 | }
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | buildToolsVersion "26.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.bilibili.draweetext.demo"
9 | minSdkVersion 15
10 | targetSdkVersion 26
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation "com.android.support:recyclerview-v7:${rootProject.ext.supportLibraryVersion}"
24 | implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibraryVersion}"
25 | implementation project(':draweetext')
26 | implementation "com.facebook.fresco:webpsupport:${rootProject.ext.frescoVersion}"
27 | implementation "com.facebook.fresco:animated-gif:${rootProject.ext.frescoVersion}" /*add gif decoding support*/
28 | }
29 |
--------------------------------------------------------------------------------
/demo/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/yrom/Documents/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 |
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/bilibili/draweetext/demo/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Bilibili
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.bilibili.draweetext.demo;
18 |
19 | import android.graphics.Color;
20 | import android.graphics.Rect;
21 | import android.graphics.drawable.ColorDrawable;
22 | import android.graphics.drawable.Drawable;
23 | import android.os.Bundle;
24 | import android.support.annotation.Nullable;
25 | import android.support.v4.app.Fragment;
26 | import android.support.v4.app.FragmentManager;
27 | import android.support.v4.app.FragmentPagerAdapter;
28 | import android.support.v4.app.ListFragment;
29 | import android.support.v4.view.ViewPager;
30 | import android.support.v7.app.AppCompatActivity;
31 | import android.support.v7.widget.GridLayoutManager;
32 | import android.support.v7.widget.RecyclerView;
33 | import android.text.SpannableStringBuilder;
34 | import android.text.Spanned;
35 | import android.text.method.ScrollingMovementMethod;
36 | import android.view.LayoutInflater;
37 | import android.view.View;
38 | import android.view.ViewGroup;
39 | import android.widget.ArrayAdapter;
40 | import android.widget.TextView;
41 |
42 | import com.bilibili.draweetext.DraweeSpan;
43 |
44 | public class MainActivity extends AppCompatActivity {
45 |
46 | @Override
47 | protected void onCreate(Bundle savedInstanceState) {
48 | super.onCreate(savedInstanceState);
49 |
50 | setContentView(R.layout.activity_main);
51 | ViewPager pager = findViewById(R.id.container);
52 | pager.setAdapter(new SectionsAdapter(getSupportFragmentManager()));
53 | }
54 |
55 |
56 | private class SectionsAdapter extends FragmentPagerAdapter {
57 |
58 | SectionsAdapter(FragmentManager fm) {
59 | super(fm);
60 | }
61 |
62 | @Override
63 | public int getCount() {
64 | return 3;
65 | }
66 |
67 | @Override
68 | public CharSequence getPageTitle(int position) {
69 | switch (position) {
70 | case 0:
71 | return "Simple";
72 | case 1:
73 | return "ListView";
74 | case 2:
75 | return "RecyclerView";
76 | default:
77 | throw new IndexOutOfBoundsException();
78 | }
79 | }
80 |
81 | @Override
82 | public Fragment getItem(int position) {
83 | switch (position) {
84 | case 0:
85 | return SimpleFragment.newInstance();
86 | case 1:
87 | return ListViewFragment.newInstance();
88 | case 2:
89 | return RecyclerViewFragment.newInstance();
90 | default:
91 | throw new IndexOutOfBoundsException();
92 | }
93 | }
94 | }
95 |
96 |
97 | public static class SimpleFragment extends Fragment {
98 | public static Fragment newInstance() {
99 | return new SimpleFragment();
100 | }
101 |
102 | @Nullable
103 | @Override
104 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
105 | return inflater.inflate(R.layout.fragment_simple, container, false);
106 | }
107 |
108 | @Override
109 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
110 | super.onViewCreated(view, savedInstanceState);
111 | final TextView textview = view.findViewById(R.id.text1);
112 | textview.setMovementMethod(ScrollingMovementMethod.getInstance());
113 | textview.setText(buildText());
114 | view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
115 | @Override
116 | public void onClick(View v) {
117 | if (v.getTag() == "") {
118 | textview.setText(buildText());
119 | v.setTag(null);
120 | } else {
121 | textview.setText(buildText2());
122 | v.setTag("");
123 | }
124 | }
125 | });
126 | }
127 |
128 | CharSequence buildText() {
129 | SpannableStringBuilder builder = new SpannableStringBuilder();
130 | builder.append("2333333333333");
131 | builder.append("\nwh=50, margin=0:www");
132 | int start = builder.length();
133 | builder.append("[img]");
134 | builder.setSpan(new DraweeSpan.Builder("http://img.yo9.com/24fe1ed09fbc11e59d8700163e00043c")
135 | .setLayout(50, 50).build(),
136 | start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
137 | builder.append("www");
138 | builder.append("\nwh=100, margin=8, align baseline:");
139 | start = builder.length();
140 | builder.append("[img]");
141 | builder.setSpan(new DraweeSpan.Builder("http://img.yo9.com/24fe1ed09fbc11e59d8700163e00043c", true)
142 | .setLayout(100, 100)
143 | .setMargin(8)
144 | .build(),
145 | start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
146 | builder.append("www");
147 | builder.append("\nwh=100, margin=4, 4, 8,webp:");
148 | start = builder.length();
149 | builder.append("[img]");
150 | builder.setSpan(new DraweeSpan.Builder("http://img.yo9.com/25126a209fbc11e59d8700163e00043c@100w.webp")
151 | .setLayout(100, 100).setMargin(4, 4, 8).build(),
152 | start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
153 | builder.append("www");
154 | builder.append("\nwh=150, margin=0:www");
155 | start = builder.length();
156 | builder.append("[img]");
157 | builder.setSpan(new DraweeSpan.Builder("http://img.yo9.com/250c9dc09fbc11e59d8700163e00043c")
158 | .setLayout(150, 150).build(),
159 | start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
160 | builder.append("www");
161 | builder.append("\n\n");
162 | start = builder.length();
163 | builder.append("[emotion:tv_cheers]");
164 | DraweeSpan span = new DraweeSpan.Builder("http://static.yo9.com/web/emotions/tv_cheers.png").build();
165 | builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
166 | builder.append("bilibili- ( ゜- ゜)つロ 乾杯~\n");
167 |
168 |
169 | builder.append("why I so diao");
170 | start = builder.length();
171 | builder.append("[img]");
172 | Drawable placeHolder = new ColorDrawable(Color.RED);
173 | span = new DraweeSpan.Builder("http://img.yo9.com/c82aa6c003d311e6ac3c00163e000cde@320w_720h.jpg")
174 | .setLayout(360, 720)
175 | .setPlaceHolderImage(placeHolder)
176 | .build();
177 | builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
178 | builder.append("sad sad");
179 | start = builder.length();
180 | builder.append("[emotion:tv_sad]");
181 | placeHolder = getResources().getDrawable(R.mipmap.ic_launcher);
182 | span = new DraweeSpan.Builder("http://static.yo9.com/web/emotions/tv_sad.png")
183 | .setLayout(150, 150)
184 | .setPlaceHolderImage(placeHolder)
185 | .build();
186 | builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
187 |
188 | builder.append("\n\n");
189 | builder.append("This is a gif, margin=10:");
190 | start = builder.length();
191 | builder.append("[gif:d559f520246811e69a4a00163e000cdb]");
192 | placeHolder = new ColorDrawable(Color.BLUE);
193 | builder.setSpan(new DraweeSpan.Builder("http://img.yo9.com/d559f520246811e69a4a00163e000cdb")
194 | .setPlaceHolderImage(placeHolder)
195 | .setLayout(200, 197)
196 | .setMargin(10)
197 | .setShowAnimaImmediately(true)
198 | .build(),
199 | start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
200 | builder.append("\nDisabled animation");
201 | start = builder.length();
202 | builder.append("[gif:d559f520246811e69a4a00163e000cdb]");
203 | placeHolder = new ColorDrawable(Color.BLUE);
204 | builder.setSpan(new DraweeSpan.Builder("http://img.yo9.com/d559f520246811e69a4a00163e000cdb")
205 | .setPlaceHolderImage(placeHolder)
206 | .setLayout(200, 197)
207 | .setShowAnimaImmediately(false)
208 | .build(),
209 | start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
210 | return builder;
211 | }
212 |
213 | CharSequence buildText2() {
214 | SpannableStringBuilder builder = new SpannableStringBuilder();
215 | builder.append("Reset text in same DraweeTextView~~~~");
216 | int start = builder.length();
217 | builder.append("[img]");
218 | builder.setSpan(new DraweeSpan.Builder("http://img.yo9.com/24fe1ed09fbc11e59d8700163e00043c")
219 | .setLayout(50, 50).build(),
220 | start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
221 | builder.append("\n\n");
222 | builder.append("This is a gif, margin=10:");
223 | start = builder.length();
224 | builder.append("[gif:d559f520246811e69a4a00163e000cdb]");
225 | Drawable placeHolder = new ColorDrawable(Color.BLUE);
226 | builder.setSpan(new DraweeSpan.Builder("http://img.yo9.com/d559f520246811e69a4a00163e000cdb")
227 | .setPlaceHolderImage(placeHolder)
228 | .setLayout(200, 197)
229 | .setMargin(10)
230 | .setShowAnimaImmediately(true)
231 | .build(),
232 | start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
233 | builder.append("\n\n");
234 | start = builder.length();
235 | builder.append("[emotion:tv_cheers]");
236 | builder.setSpan(new DraweeSpan.Builder("http://static.yo9.com/web/emotions/tv_cheers.png").build()
237 | , start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
238 | builder.append("bilibili- ( ゜- ゜)つロ 乾杯~\n");
239 | return builder;
240 | }
241 |
242 | }
243 |
244 | static String[] EMOTIONS = new String[]{
245 | "http://i0.hdslb.com/bfs/vip/f80d384875183dfe2e24be13011c595c0210d273.png@.webp",
246 | "http://i0.hdslb.com/bfs/vip/eb41a8c04840e4f77e76a4bff7a29ac89c432f4e.png@.webp",
247 | "http://i0.hdslb.com/bfs/vip/16b8794be990cefa6caeba4d901b934a227ee3b8.png@.webp",
248 | "http://i0.hdslb.com/bfs/vip/d1628c43d35b1530c0504a643ff80b6189fa0a43.png@.webp",
249 | "http://i0.hdslb.com/bfs/vip/fdb5870f32cfaf7949e0f88a13f6feba4a48b719.png@.webp",
250 | "http://i0.hdslb.com/bfs/vip/3754ee6e5985bd0bd7dfb668981f2a8733398ebd.png@.webp",
251 | "http://i0.hdslb.com/bfs/vip/0b41f509351958dbb63d472fec0132d1bd03bd14.png@.webp",
252 | "http://i0.hdslb.com/bfs/vip/6f058f78bce5d1c9b370c3807c891e685bb68a17.png",
253 | "http://i0.hdslb.com/bfs/vip/7f482b82a3de44ae14537cbafcbc40cf65f7113e.png",
254 | "http://i0.hdslb.com/bfs/vip/458982f20f0b7dc68c0ddac89f51ecb7c3d16a83.png",
255 | "http://i0.hdslb.com/bfs/vip/6846363907204271f0a57472744642c8882b4019.png",
256 | "http://i0.hdslb.com/bfs/vip/de3aee88f7b6cc20ba9480c96c02f83a844381a9.png",
257 | "http://i0.hdslb.com/bfs/vip/7a4cb0b644214d476ce198ddf6a7a0aa31311199.png",
258 | "http://i0.hdslb.com/bfs/vip/a695fe1301aab2675ab6f6e34757c25a863a8617.png@.webp",
259 | "http://i0.hdslb.com/bfs/vip/77545a5e420e2c43e0e4a7996a71769638ae3f90.png",
260 | "http://i0.hdslb.com/bfs/vip/af8f017e383a1999e26a7f91c3ec3c83fbb7ba77.png",
261 | "http://static.yo9.com/web/static/emotions/tv_cheers.png",
262 | "http://static.yo9.com/web/static/emotions/tv_huaji.png",
263 | "http://static.yo9.com/web/static/emotions/tv_mygod.png",
264 | "http://static.yo9.com/web/static/emotions/tv_angry.png",
265 | "http://static.yo9.com/web/static/emotions/tv_thinking.png",
266 | "http://static.yo9.com/web/static/emotions/tv_zhuangb.png",
267 | "http://static.yo9.com/web/static/emotions/tv_shocking.png",
268 | "http://static.yo9.com/web/static/emotions/tv_pill.png",
269 | "http://static.yo9.com/web/static/emotions/tv_kira.png",
270 | "http://static.yo9.com/web/static/emotions/tv_ji.png",
271 | };
272 |
273 | public static class ListViewFragment extends ListFragment {
274 |
275 | public static Fragment newInstance() {
276 | return new ListViewFragment();
277 | }
278 |
279 | @Override
280 | public void onCreate(@Nullable Bundle savedInstanceState) {
281 | super.onCreate(savedInstanceState);
282 | setListAdapter(new ArrayAdapter<>(getContext(), R.layout.simple_text,
283 | buildArray()));
284 | }
285 |
286 | private CharSequence[] buildArray() {
287 | CharSequence[] sequences = new CharSequence[EMOTIONS.length * 2];
288 | for (int i = 0; i < sequences.length; i++) {
289 | SpannableStringBuilder builder = new SpannableStringBuilder();
290 | String emotion = EMOTIONS[i % EMOTIONS.length];
291 | builder.append(emotion).append('\n');
292 | int start = builder.length();
293 | builder.append("[emotion]");
294 | builder.setSpan(new DraweeSpan.Builder(emotion).setLayout(140, 140).build(), start, builder.length(),
295 | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
296 | builder.append("~~~~~~~~~~~~~~~~~~");
297 | sequences[i] = builder;
298 | }
299 | return sequences;
300 | }
301 | }
302 |
303 | public static class RecyclerViewFragment extends Fragment {
304 | @Nullable
305 | @Override
306 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
307 | return inflater.inflate(R.layout.fragment_recycler, container, false);
308 | }
309 |
310 | @Override
311 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
312 | super.onViewCreated(view, savedInstanceState);
313 | RecyclerView recyclerView = view.findViewById(R.id.recycler);
314 | recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
315 | recyclerView.setHasFixedSize(true);
316 |
317 | recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
318 | @Override
319 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
320 | outRect.inset(8, 8);
321 | }
322 | });
323 | final CharSequence[] sequences = new CharSequence[EMOTIONS.length * 2];
324 | for (int i = 0; i < sequences.length; i++) {
325 | SpannableStringBuilder builder = new SpannableStringBuilder();
326 | String emotion = EMOTIONS[i % EMOTIONS.length];
327 | builder.append(String.valueOf(i)).append("~~~").append('\n');
328 | int start = builder.length();
329 | builder.append("[emotion]");
330 | builder.setSpan(new DraweeSpan.Builder(emotion).setLayout(120, 120).build(), start, builder.length(),
331 | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
332 | builder.append("\n~~~~~");
333 | sequences[i] = builder;
334 | }
335 | recyclerView.setAdapter(new RecyclerView.Adapter() {
336 |
337 | @Override
338 | public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
339 | return new TextViewHolder(LayoutInflater.from(parent.getContext())
340 | .inflate(R.layout.simple_text, parent, false));
341 | }
342 |
343 | @Override
344 | public void onBindViewHolder(TextViewHolder holder, int position) {
345 | holder.text.setText(sequences[position]);
346 | }
347 |
348 | @Override
349 | public int getItemCount() {
350 | return sequences.length;
351 | }
352 | });
353 | }
354 |
355 | static class TextViewHolder extends RecyclerView.ViewHolder {
356 | TextView text;
357 |
358 | TextViewHolder(View itemView) {
359 | super(itemView);
360 | text = (TextView) itemView;
361 | }
362 | }
363 |
364 | public static Fragment newInstance() {
365 | return new RecyclerViewFragment();
366 | }
367 | }
368 | }
369 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/bilibili/draweetext/demo/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.bilibili.draweetext.demo;
2 |
3 | import android.app.Application;
4 | import android.util.Log;
5 |
6 | import com.facebook.common.logging.FLog;
7 | import com.facebook.common.logging.FLogDefaultLoggingDelegate;
8 | import com.facebook.drawee.backends.pipeline.Fresco;
9 | import com.facebook.imagepipeline.core.ImagePipelineConfig;
10 |
11 | /**
12 | * @author yrom.
13 | */
14 | public class MainApplication extends Application {
15 | @Override
16 | public void onCreate() {
17 | super.onCreate();
18 | // initialize fresco with enabled webp
19 | Fresco.initialize(this, ImagePipelineConfig.newBuilder(this)
20 | .experiment()
21 | .setWebpSupportEnabled(true)
22 | .build());
23 | // for debug
24 | if (BuildConfig.DEBUG) {
25 | FLogDefaultLoggingDelegate.getInstance().setApplicationTag("Drawee-text");
26 | FLog.setMinimumLoggingLevel(Log.VERBOSE);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/fragment_recycler.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/fragment_simple.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
14 |
21 |
22 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/simple_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/drawee-text-view/7126d19fc4cbcf5160ef29a5f828d23a0e7611dd/demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/drawee-text-view/7126d19fc4cbcf5160ef29a5f828d23a0e7611dd/demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/drawee-text-view/7126d19fc4cbcf5160ef29a5f828d23a0e7611dd/demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/drawee-text-view/7126d19fc4cbcf5160ef29a5f828d23a0e7611dd/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/drawee-text-view/7126d19fc4cbcf5160ef29a5f828d23a0e7611dd/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DraweeTextView
3 |
4 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/draweetext/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/draweetext/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 26
5 | buildToolsVersion "26.0.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 15
9 | targetSdkVersion 26
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | compileOptions {
20 | sourceCompatibility JavaVersion.VERSION_1_7
21 | targetCompatibility JavaVersion.VERSION_1_7
22 | }
23 | }
24 |
25 | dependencies {
26 | testImplementation 'junit:junit:4.12'
27 | testImplementation 'org.mockito:mockito-core:2.8.47'
28 | testImplementation 'org.robolectric:robolectric:3.4.2'
29 | api "com.android.support:support-core-utils:${rootProject.ext.supportLibraryVersion}"
30 | api "com.facebook.fresco:fresco:${rootProject.ext.frescoVersion}" /*required*/
31 | api "com.facebook.fresco:animated-base:${rootProject.ext.frescoVersion}" /*required*/
32 | }
33 |
--------------------------------------------------------------------------------
/draweetext/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/yrom/Documents/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 |
--------------------------------------------------------------------------------
/draweetext/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/draweetext/src/main/java/com/bilibili/draweetext/DraweeSpan.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Bilibili
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.bilibili.draweetext;
17 |
18 | import android.content.Context;
19 | import android.graphics.Bitmap;
20 | import android.graphics.Canvas;
21 | import android.graphics.Color;
22 | import android.graphics.Paint;
23 | import android.graphics.Point;
24 | import android.graphics.Rect;
25 | import android.graphics.drawable.Animatable;
26 | import android.graphics.drawable.BitmapDrawable;
27 | import android.graphics.drawable.ColorDrawable;
28 | import android.graphics.drawable.Drawable;
29 | import android.net.Uri;
30 | import android.support.annotation.NonNull;
31 | import android.support.annotation.Nullable;
32 | import android.support.annotation.VisibleForTesting;
33 | import android.text.TextUtils;
34 | import android.text.style.DynamicDrawableSpan;
35 | import android.util.Log;
36 |
37 | import com.facebook.common.executors.UiThreadImmediateExecutorService;
38 | import com.facebook.common.logging.FLog;
39 | import com.facebook.common.references.CloseableReference;
40 | import com.facebook.datasource.BaseDataSubscriber;
41 | import com.facebook.datasource.DataSource;
42 | import com.facebook.datasource.DataSubscriber;
43 | import com.facebook.drawable.base.DrawableWithCaches;
44 | import com.facebook.drawee.components.DeferredReleaser;
45 | import com.facebook.drawee.drawable.ForwardingDrawable;
46 | import com.facebook.drawee.drawable.OrientedDrawable;
47 | import com.facebook.imagepipeline.animated.base.AnimatedImageResult;
48 | import com.facebook.imagepipeline.common.ImageDecodeOptions;
49 | import com.facebook.imagepipeline.core.ImagePipelineFactory;
50 | import com.facebook.imagepipeline.drawable.DrawableFactory;
51 | import com.facebook.imagepipeline.image.CloseableAnimatedImage;
52 | import com.facebook.imagepipeline.image.CloseableImage;
53 | import com.facebook.imagepipeline.image.CloseableStaticBitmap;
54 | import com.facebook.imagepipeline.request.ImageRequest;
55 | import com.facebook.imagepipeline.request.ImageRequestBuilder;
56 |
57 | /**
58 | * Like {@link com.facebook.drawee.interfaces.DraweeHierarchy} that displays a placeholder
59 | * until actual image is set.
60 | *
61 | * Usage in DraweeTextView's text.
62 | *
63 | * @author yrom
64 | */
65 | public class DraweeSpan extends DynamicDrawableSpan implements DeferredReleaser.Releasable {
66 | private static final String TAG = "DraweeSpan";
67 |
68 | private final DeferredReleaser mDeferredReleaser;
69 | private final ForwardingDrawable mActualDrawable;
70 | private CloseableReference mFetchedImage;
71 | private DataSource> mDataSource;
72 | private boolean mIsRequestSubmitted;
73 | private Drawable mDrawable;
74 | private Drawable mPlaceHolder;
75 | private DraweeTextView mAttachedView;
76 | private String mImageUri;
77 | private Point mLayout = new Point();
78 | private Rect mMargin = new Rect();
79 | private boolean mIsAttached;
80 | private boolean mShouldShowAnim = false;
81 |
82 | /**
83 | * Use {@link Builder} to build a DraweeSpan.
84 | */
85 | private DraweeSpan(String uri, int verticalAlignment, Drawable placeHolder, boolean showAnim) {
86 | super(verticalAlignment);
87 | mImageUri = uri;
88 | mShouldShowAnim = showAnim;
89 | mDeferredReleaser = DeferredReleaser.getInstance();
90 | mPlaceHolder = placeHolder;
91 | // create forwarding drawable with placeholder
92 | mActualDrawable = new ForwardingDrawable(mPlaceHolder);
93 | }
94 |
95 | protected void layout() {
96 | mActualDrawable.setBounds(0, 0, mLayout.x, mLayout.y);
97 | }
98 |
99 | @Override
100 | public Drawable getDrawable() {
101 | return mActualDrawable;
102 | }
103 |
104 | @Override
105 | public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
106 | Drawable d = getDrawable();
107 | Rect rect = d.getBounds();
108 |
109 | if (fm != null) {
110 | fm.ascent = -rect.bottom - mMargin.top;
111 | fm.descent = 0;
112 |
113 | fm.top = fm.ascent;
114 | fm.bottom = 0;
115 | }
116 |
117 | return rect.right + mMargin.left + mMargin.right;
118 | }
119 |
120 | @Override
121 | public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
122 | super.draw(canvas, text, start, end, x + mMargin.left, top, y, bottom, paint);
123 | }
124 |
125 | public void setImage(Drawable drawable) {
126 | if (mDrawable != drawable) {
127 | releaseDrawable(mDrawable);
128 | setDrawableInner(drawable);
129 | if (drawable instanceof Animatable) {
130 | ((Animatable) drawable).start();
131 | }
132 | mDrawable = drawable;
133 | }
134 | }
135 |
136 | private void setDrawableInner(Drawable drawable) {
137 | if(drawable == null) {
138 | return;
139 | }
140 | mActualDrawable.setDrawable(drawable);
141 | }
142 |
143 | public void reset() {
144 | setDrawableInner(mPlaceHolder);
145 | }
146 |
147 | public void onAttach(@NonNull DraweeTextView view) {
148 | mIsAttached = true;
149 | if (mAttachedView != view) {
150 | mActualDrawable.setCallback(null);
151 | if (mAttachedView != null) {
152 | throw new IllegalStateException("has been attached to view:" + mAttachedView);
153 | }
154 | mAttachedView = view;
155 | setDrawableInner(mDrawable);
156 | mActualDrawable.setCallback(mAttachedView);
157 | }
158 | mDeferredReleaser.cancelDeferredRelease(this);
159 | if (!mIsRequestSubmitted) {
160 | submitRequest();
161 | } else if (mShouldShowAnim && mDrawable instanceof Animatable) {
162 | ((Animatable) mDrawable).start();
163 | }
164 | }
165 |
166 | private void submitRequest() {
167 | if(TextUtils.isEmpty(getImageUri())) {
168 | return;
169 | }
170 |
171 | mIsRequestSubmitted = true;
172 | final String id = getId();
173 | mDataSource = fetchDecodedImage();
174 | DataSubscriber> subscriber
175 | = new BaseDataSubscriber>() {
176 | @Override
177 | protected void onNewResultImpl(DataSource> dataSource) {
178 | boolean isFinished = dataSource.isFinished();
179 | CloseableReference result = dataSource.getResult();
180 | if (result != null) {
181 | onNewResultInternal(id, dataSource, result, isFinished);
182 | } else if (isFinished) {
183 | onFailureInternal(id, dataSource, new NullPointerException(), /* isFinished */ true);
184 | }
185 | }
186 |
187 | @Override
188 | protected void onFailureImpl(DataSource> dataSource) {
189 | onFailureInternal(id, dataSource, dataSource.getFailureCause(), /* isFinished */ true);
190 | }
191 | };
192 | mDataSource.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
193 | }
194 |
195 | @VisibleForTesting
196 | protected DataSource> fetchDecodedImage() {
197 | ImagePipelineFactory factory;
198 | try {
199 | factory = ImagePipelineFactory.getInstance();
200 | } catch (NullPointerException e) {
201 | // Image pipeline is not initialized
202 | ImagePipelineFactory.initialize(mAttachedView.getContext().getApplicationContext());
203 | factory = ImagePipelineFactory.getInstance();
204 | }
205 | ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(getImageUri()))
206 | .setImageDecodeOptions(ImageDecodeOptions.newBuilder().setDecodePreviewFrame(true).build())
207 | .build();
208 | return factory.getImagePipeline().fetchDecodedImage(request, null);
209 | }
210 |
211 | @NonNull
212 | public String getImageUri() {
213 | return mImageUri;
214 | }
215 |
216 | protected String getId() {
217 | return String.valueOf(getImageUri().hashCode());
218 | }
219 |
220 | private void onFailureInternal(String id,
221 | DataSource> dataSource,
222 | Throwable throwable, boolean isFinished) {
223 | if (FLog.isLoggable(Log.WARN)) {
224 | FLog.w(DraweeSpan.class, id + " load failure", throwable);
225 | }
226 | // ignored this result
227 | if (!getId().equals(id)
228 | || dataSource != mDataSource
229 | || !mIsRequestSubmitted) {
230 | dataSource.close();
231 | return;
232 | }
233 | if (isFinished) {
234 | mDataSource = null;
235 | // Set the previously available image if available.
236 | setDrawableInner(mDrawable);
237 | }
238 | }
239 |
240 | private void onNewResultInternal(String id,
241 | DataSource> dataSource,
242 | CloseableReference result,
243 | boolean isFinished) {
244 | // ignored this result
245 | if (!getId().equals(id)
246 | || dataSource != mDataSource
247 | || !mIsRequestSubmitted) {
248 | CloseableReference.closeSafely(result);
249 | dataSource.close();
250 | return;
251 | }
252 | Drawable drawable;
253 | try {
254 | drawable = createDrawable(result);
255 | } catch (Exception exception) {
256 | CloseableReference.closeSafely(result);
257 | onFailureInternal(id, dataSource, exception, isFinished);
258 | return;
259 | }
260 | CloseableReference previousImage = mFetchedImage;
261 | Drawable previousDrawable = mDrawable;
262 | mFetchedImage = result;
263 | try {
264 | // set the new image
265 | if (isFinished) {
266 | mDataSource = null;
267 | setImage(drawable);
268 | }
269 | } finally {
270 | if (previousDrawable != null && previousDrawable != drawable) {
271 | releaseDrawable(previousDrawable);
272 | }
273 | if (previousImage != null && previousImage != result) {
274 | CloseableReference.closeSafely(previousImage);
275 | }
276 | }
277 | }
278 |
279 | private Drawable createDrawable(CloseableReference result) {
280 | CloseableImage closeableImage = result.get();
281 | if (closeableImage instanceof CloseableStaticBitmap) {
282 | CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) closeableImage;
283 | BitmapDrawable bitmapDrawable = createBitmapDrawable(closeableStaticBitmap.getUnderlyingBitmap());
284 | return (closeableStaticBitmap.getRotationAngle() != 0 && closeableStaticBitmap.getRotationAngle() != -1
285 | ? new OrientedDrawable(bitmapDrawable, closeableStaticBitmap.getRotationAngle()) : bitmapDrawable);
286 | } else if (closeableImage instanceof CloseableAnimatedImage) {
287 | if (mShouldShowAnim) {
288 | DrawableFactory factory = ImagePipelineFactory.getInstance().getAnimatedDrawableFactory(mAttachedView.getContext());
289 | if (factory != null) {
290 | return factory.createDrawable(closeableImage);
291 | }
292 | }
293 | AnimatedImageResult image = ((CloseableAnimatedImage) closeableImage).getImageResult();
294 | int frame = image.getFrameForPreview();
295 | CloseableReference bitmap = null;
296 | if (frame >= 0) {
297 | bitmap = image.getDecodedFrame(frame);
298 | }
299 | if (bitmap == null) {
300 | bitmap = image.getPreviewBitmap();
301 | }
302 | if (bitmap != null && bitmap.get() != null) {
303 | BitmapDrawable bitmapDrawable = createBitmapDrawable(bitmap.get());
304 | return bitmapDrawable;
305 | }
306 | }
307 | throw new UnsupportedOperationException("Unrecognized image class: " + closeableImage);
308 | }
309 |
310 | protected BitmapDrawable createBitmapDrawable(Bitmap bitmap) {
311 | BitmapDrawable drawable;
312 | if (mAttachedView != null) {
313 | final Context context = mAttachedView.getContext();
314 | drawable = new BitmapDrawable(context.getResources(), bitmap);
315 | } else {
316 | // can't happen
317 | drawable = new BitmapDrawable(null, bitmap);
318 | }
319 | return drawable;
320 | }
321 |
322 | public void onDetach() {
323 | if (!mIsAttached)
324 | return;
325 | if (mShouldShowAnim && mDrawable instanceof Animatable) {
326 | ((Animatable) mDrawable).stop();
327 | }
328 | mActualDrawable.setCallback(null);
329 | mAttachedView = null;
330 | reset();
331 | mDeferredReleaser.scheduleDeferredRelease(this);
332 | }
333 |
334 | @Override
335 | public void release() {
336 | mIsRequestSubmitted = false;
337 | mIsAttached = false;
338 | mAttachedView = null;
339 | if (mDataSource != null) {
340 | mDataSource.close();
341 | mDataSource = null;
342 | }
343 | if (mDrawable != null) {
344 | releaseDrawable(mDrawable);
345 | }
346 | mDrawable = null;
347 | if (mFetchedImage != null) {
348 | CloseableReference.closeSafely(mFetchedImage);
349 | mFetchedImage = null;
350 | }
351 | }
352 |
353 | void releaseDrawable(@Nullable Drawable drawable) {
354 | if (drawable instanceof Animatable && ((Animatable) drawable).isRunning()) {
355 | ((Animatable) drawable).stop();
356 | }
357 | if (drawable instanceof DrawableWithCaches) {
358 | ((DrawableWithCaches) drawable).dropCaches();
359 | }
360 | }
361 |
362 | /**
363 | * DraweeSpan builder.
364 | */
365 | public static class Builder {
366 | String uri;
367 | int width = 100;
368 | int height = 100;
369 | int verticalAlignment = ALIGN_BOTTOM;
370 | Drawable placeholder;
371 | boolean showAnim;
372 | Rect margin = new Rect();
373 |
374 | public Builder(String uri) {
375 | this(uri, false);
376 | }
377 |
378 | /**
379 | * Construct drawee span builder.
380 | *
381 | * @param uri image uri.
382 | * @param alignBaseline true to set {@link #ALIGN_BASELINE}, otherwise {@link #ALIGN_BOTTOM} .
383 | */
384 | public Builder(String uri, boolean alignBaseline) {
385 | this.uri = uri;
386 | if (uri == null) {
387 | throw new NullPointerException("Attempt to create a DraweeSpan with null uri string!");
388 | }
389 | if (alignBaseline) {
390 | this.verticalAlignment = ALIGN_BASELINE;
391 | }
392 | }
393 |
394 | /**
395 | * @param width width of this span, px
396 | * @param height height of this span, px
397 | */
398 | public Builder setLayout(int width, int height) {
399 | this.width = width;
400 | this.height = height;
401 | return this;
402 | }
403 |
404 | /**
405 | * You can set margin in left, right and top in px. Bottom is in baseline.
406 | */
407 | public Builder setMargin(int margin) {
408 | this.margin.set(margin, margin, margin, 0);
409 | return this;
410 | }
411 |
412 | /**
413 | * You can set margin in left, right and top in px. Bottom is in baseline.
414 | */
415 | public Builder setMargin(int left, int top, int right) {
416 | this.margin.set(left, top, right, 0);
417 | return this;
418 | }
419 |
420 | /**
421 | * @param placeholder The drawable shows on loading image {@code uri}
422 | */
423 | public Builder setPlaceHolderImage(Drawable placeholder) {
424 | this.placeholder = placeholder;
425 | return this;
426 | }
427 |
428 | /**
429 | * Show anim in animate drawable.
430 | * @param showAnim If set true, gif image would animate immediately.
431 | */
432 | public Builder setShowAnimaImmediately(boolean showAnim) {
433 | this.showAnim = showAnim;
434 | return this;
435 | }
436 |
437 | public DraweeSpan build() {
438 | if (placeholder == null) {
439 | placeholder = new ColorDrawable(Color.TRANSPARENT);
440 | placeholder.setBounds(0, 0, width, height);
441 | }
442 | DraweeSpan span = new DraweeSpan(uri, verticalAlignment, placeholder, showAnim);
443 | span.mLayout.set(width, height);
444 | span.mMargin.set(margin.left, margin.top, margin.right, 0);
445 | span.layout();
446 | return span;
447 | }
448 | }
449 | }
--------------------------------------------------------------------------------
/draweetext/src/main/java/com/bilibili/draweetext/DraweeTextView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Bilibili
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.bilibili.draweetext;
17 |
18 | import android.annotation.TargetApi;
19 | import android.content.Context;
20 | import android.graphics.drawable.Animatable;
21 | import android.graphics.drawable.Drawable;
22 | import android.os.Build;
23 | import android.text.Spanned;
24 | import android.util.AttributeSet;
25 | import android.widget.TextView;
26 |
27 | import com.facebook.drawee.drawable.ForwardingDrawable;
28 |
29 | /**
30 | * Like {@link com.facebook.drawee.view.DraweeView} that displays drawables {@link DraweeSpan} but surrounded with text.
31 | *
32 | * @author yrom
33 | */
34 | public class DraweeTextView extends TextView {
35 | public DraweeTextView(Context context) {
36 | super(context);
37 | }
38 |
39 | public DraweeTextView(Context context, AttributeSet attrs) {
40 | super(context, attrs);
41 | }
42 |
43 | public DraweeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
44 | super(context, attrs, defStyleAttr);
45 | }
46 |
47 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
48 | public DraweeTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
49 | super(context, attrs, defStyleAttr, defStyleRes);
50 | }
51 |
52 | private boolean mHasDraweeInText;
53 | // detect drawee-spans has been attached or not
54 | private boolean mIsSpanAttached;
55 |
56 | @Override
57 | public void setText(CharSequence text, BufferType type) {
58 | boolean wasSpanAttached = mIsSpanAttached;
59 | if (mHasDraweeInText && wasSpanAttached) {
60 | onDetach(); // detach all old images
61 | mHasDraweeInText = false;
62 | }
63 | if (text instanceof Spanned) {
64 | // find DraweeSpan in text
65 | DraweeSpan[] spans = ((Spanned) text).getSpans(0, text.length(), DraweeSpan.class);
66 | mHasDraweeInText = spans.length > 0;
67 | }
68 | super.setText(text, type);
69 | if (mHasDraweeInText && wasSpanAttached) {
70 | onAttach(); // reattach drawee spans
71 | }
72 | }
73 |
74 | @Override
75 | protected void onAttachedToWindow() {
76 | super.onAttachedToWindow();
77 | onAttach();
78 | }
79 |
80 | @Override
81 | protected void onDetachedFromWindow() {
82 | super.onDetachedFromWindow();
83 | onDetach();
84 | }
85 |
86 |
87 | @Override
88 | public void onStartTemporaryDetach() {
89 | super.onStartTemporaryDetach();
90 | onDetach();
91 | }
92 |
93 | @Override
94 | public void onFinishTemporaryDetach() {
95 | super.onFinishTemporaryDetach();
96 | onAttach();
97 | }
98 |
99 | @Override
100 | public void invalidateDrawable(Drawable dr) {
101 | if (mHasDraweeInText) {
102 | /* invalidate the whole view in this case because it's very
103 | * hard to know what the bounds of drawables actually is.
104 | */
105 | invalidate();
106 | } else {
107 | super.invalidateDrawable(dr);
108 | }
109 | }
110 |
111 | @Override
112 | protected boolean verifyDrawable(Drawable who) {
113 | return super.verifyDrawable(who) || mHasDraweeInText
114 | // only schedule animation on AnimatableDrawable
115 | && (who instanceof ForwardingDrawable && who.getCurrent() instanceof Animatable);
116 | }
117 |
118 | /**
119 | * Attach DraweeSpans in text
120 | */
121 | final void onAttach() {
122 | DraweeSpan[] images = getImages();
123 | for (DraweeSpan image : images) {
124 | image.onAttach(this);
125 | }
126 | mIsSpanAttached = true;
127 | }
128 |
129 | private DraweeSpan[] getImages() {
130 | if (mHasDraweeInText && length() > 0)
131 | return ((Spanned) getText()).getSpans(0, length(), DraweeSpan.class);
132 | return new DraweeSpan[0]; //TODO: pool empty typed array
133 | }
134 |
135 | /**
136 | * Detach all of the DraweeSpans in text
137 | */
138 | final void onDetach() {
139 | DraweeSpan[] images = getImages();
140 | for (DraweeSpan image : images) {
141 | Drawable drawable = image.getDrawable();
142 | // reset callback first
143 | if (drawable != null) {
144 | unscheduleDrawable(drawable);
145 | }
146 | image.onDetach();
147 | }
148 | mIsSpanAttached = false;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/draweetext/src/test/java/com/bilibili/draweetext/DraweeSpanTest.java:
--------------------------------------------------------------------------------
1 | package com.bilibili.draweetext;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Rect;
6 | import android.graphics.drawable.Drawable;
7 |
8 | import com.facebook.common.references.CloseableReference;
9 | import com.facebook.common.references.ResourceReleaser;
10 | import com.facebook.datasource.DataSource;
11 | import com.facebook.datasource.DataSources;
12 | import com.facebook.imagepipeline.image.CloseableImage;
13 | import com.facebook.imagepipeline.image.CloseableStaticBitmap;
14 | import com.facebook.imagepipeline.image.ImmutableQualityInfo;
15 |
16 | import org.junit.Test;
17 | import org.junit.runner.RunWith;
18 | import org.mockito.Mockito;
19 | import org.robolectric.Robolectric;
20 | import org.robolectric.RobolectricTestRunner;
21 | import org.robolectric.annotation.Config;
22 | import org.robolectric.shadows.ShadowBitmap;
23 | import org.robolectric.shadows.ShadowLooper;
24 |
25 | import static org.junit.Assert.assertEquals;
26 | import static org.junit.Assert.assertFalse;
27 | import static org.junit.Assert.assertTrue;
28 | import static org.mockito.Mockito.doReturn;
29 | import static org.mockito.Mockito.mock;
30 | import static org.mockito.Mockito.never;
31 | import static org.mockito.Mockito.spy;
32 | import static org.mockito.Mockito.verify;
33 |
34 | /**
35 | * @author yrom.
36 | */
37 | @RunWith(RobolectricTestRunner.class)
38 | @Config(manifest = Config.NONE)
39 | public class DraweeSpanTest {
40 | @Test
41 | public void testBuild() {
42 | Drawable ph = mock(Drawable.class);
43 | DraweeSpan span = new DraweeSpan.Builder("http://test")
44 | .setLayout(100, 100)
45 | .setPlaceHolderImage(ph)
46 | .build();
47 | assertEquals("http://test", span.getImageUri());
48 | assertEquals(ph, span.getDrawable().getCurrent());
49 | assertEquals(new Rect(0, 0, 100, 100), span.getDrawable().getBounds());
50 | }
51 |
52 | @Test
53 | public void testLifeCycle(){
54 | Activity activity = Robolectric.buildActivity(Activity.class).create().get();
55 | @SuppressWarnings("unchecked")
56 | ResourceReleaser releaser = mock(ResourceReleaser.class);
57 | Bitmap bitmap = ShadowBitmap.createBitmap(1,1, Bitmap.Config.ARGB_8888);
58 | CloseableImage image = Mockito.spy(new CloseableStaticBitmap(
59 | bitmap,
60 | releaser,
61 | ImmutableQualityInfo.FULL_QUALITY, 0));
62 |
63 | DraweeTextView textview = new DraweeTextView(activity);
64 | DraweeSpan span = new DraweeSpan.Builder("http://test")
65 | .build();
66 | DraweeSpan spy = spy(span);
67 | DataSource> dataSource = DataSources.immediateDataSource(CloseableReference.of(image));
68 | assertFalse(dataSource.isClosed());
69 | doReturn(dataSource)
70 | .when(spy).fetchDecodedImage();
71 | spy.onAttach(textview);
72 | ShadowLooper.runUiThreadTasks();
73 | verify(spy).createBitmapDrawable(bitmap);
74 | verify(image, never()).close();
75 | spy.onDetach();
76 | verify(spy).reset();
77 | ShadowLooper.runUiThreadTasks();
78 | verify(image).close();
79 | verify(releaser).release(bitmap);
80 | assertTrue(dataSource.isClosed());
81 | }
82 | }
--------------------------------------------------------------------------------
/draweetext/src/test/java/com/bilibili/draweetext/DraweeTextViewTest.java:
--------------------------------------------------------------------------------
1 | package com.bilibili.draweetext;
2 |
3 | import android.app.Activity;
4 | import android.text.SpannableStringBuilder;
5 | import android.text.Spanned;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.InOrder;
11 | import org.mockito.Mockito;
12 | import org.robolectric.Robolectric;
13 | import org.robolectric.RobolectricTestRunner;
14 | import org.robolectric.annotation.Config;
15 |
16 | /**
17 | * @author yrom.
18 | */
19 | @RunWith(RobolectricTestRunner.class)
20 | @Config(manifest = Config.NONE)
21 | public class DraweeTextViewTest {
22 |
23 | private DraweeTextView mText;
24 |
25 | @Before
26 | public void setup() {
27 | Activity activity = Robolectric.buildActivity(Activity.class).create().get();
28 | mText = new DraweeTextView(activity);
29 |
30 | }
31 |
32 | @Test
33 | public void testLifeCycle_spanned() {
34 | DraweeSpan span1 = Mockito.mock(DraweeSpan.class);
35 | DraweeSpan span2 = Mockito.mock(DraweeSpan.class);
36 | SpannableStringBuilder text = new SpannableStringBuilder("adfadfadfadfasdfbaadfadfasdff");
37 | text.setSpan(span1, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
38 | text.setSpan(span2, 6, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
39 | InOrder inOrder = Mockito.inOrder(span1, span2);
40 | mText.setText(text);
41 | // should never called attach or detach before textview 'attachToWindow'
42 | Mockito.verify(span1, Mockito.never()).onDetach();
43 | Mockito.verify(span1, Mockito.never()).onAttach(mText);
44 | mText.onAttachedToWindow();
45 | inOrder.verify(span1).onAttach(mText);
46 | inOrder.verify(span2).onAttach(mText);
47 | mText.onStartTemporaryDetach();
48 | inOrder.verify(span1).onDetach();
49 | inOrder.verify(span2).onDetach();
50 | mText.onFinishTemporaryDetach();
51 | inOrder.verify(span1).onAttach(mText);
52 | inOrder.verify(span2).onAttach(mText);
53 | mText.onDetachedFromWindow();
54 | inOrder.verify(span1).onDetach();
55 | inOrder.verify(span2).onDetach();
56 | }
57 |
58 | @Test
59 | public void testLifeCycle_resetText() {
60 | DraweeSpan span1 = Mockito.mock(DraweeSpan.class);
61 | DraweeSpan span2 = Mockito.mock(DraweeSpan.class);
62 | SpannableStringBuilder text1 = new SpannableStringBuilder("adfadfadfadfafadfasdff");
63 | text1.setSpan(span1, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
64 | SpannableStringBuilder text2 = new SpannableStringBuilder("adfadfadfadfafadfasdff");
65 | text2.setSpan(span2, 2, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
66 |
67 | mText.setText(text1);
68 | mText.onAttachedToWindow();
69 | InOrder inOrder = Mockito.inOrder(span1, span2);
70 | inOrder.verify(span1).onAttach(mText);
71 | // reset text in same textview
72 | mText.setText(text2);
73 | inOrder.verify(span1).onDetach();
74 | inOrder.verify(span2).onAttach(mText);
75 | mText.onDetachedFromWindow();
76 | inOrder.verify(span2).onDetach();
77 | }
78 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/drawee-text-view/7126d19fc4cbcf5160ef29a5f828d23a0e7611dd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Nov 13 11:19:21 CST 2017
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-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':demo',':draweetext'
2 |
--------------------------------------------------------------------------------