Commit e39c45b3 authored by 王韦's avatar 王韦

[new] 优化pop base

parent 99653783
......@@ -35,4 +35,6 @@ dependencies {
// implementation 'com.qmai.android.sdk:widgetlib:1.1.2-SNAPSHOT'
implementation project(path: ':tools')
implementation 'androidx.recyclerview:recyclerview:1.0.0'
}
......@@ -4,10 +4,13 @@ import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.qimai.android.tools.V
import com.qimai.android.widgetlib.popup.CommonPopWindow
import com.qimai.android.widgetlib.popup.QmAlertDialog
class CommomPopActivity : AppCompatActivity() {
var dialog: QmAlertDialog? = null
var i = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_commom_pop)
......@@ -26,30 +29,51 @@ class CommomPopActivity : AppCompatActivity() {
// .build()
// commonPopWindow.show(findViewById(R.id.root))
// commonPopWindow.dimBehind(0.5f)
//
QmAlertDialog
.Builder()
.withContext(this)
.withLeftButton(leftButtonText = "取消", clickListener = {
Toast.makeText(this, "取消", Toast.LENGTH_SHORT).show()
it.dismiss()
})
.withRightButton(rightButtonText = "确定", clickListener = {
i = System.currentTimeMillis()
"$i".V()
// i.hashCode().toString().V()
if (dialog == null) {
val right: (CommonPopWindow: CommonPopWindow) -> Unit = {
Toast.makeText(this, "确定", Toast.LENGTH_SHORT).show()
it.dismiss()
"$i".V()
// "---------${i.hashCode()}".V()
}
dialog = QmAlertDialog
.Builder()
.withContext(this)
.withLeftButton(leftButtonText = "取消", clickListener = {
Toast.makeText(this, "取消", Toast.LENGTH_SHORT).show()
it.dismiss()
})
.withRightButton(rightButtonText = "确定", clickListener = right).withRootView(
findViewById(R.id.root)
)
.withTitle("订单1111")
.withSubTitle("子标题")
.withWidth(400)
.build()
}
dialog?.show()
}).withRootView(
findViewById(R.id.root)
)
.withTitle("订单1111")
.withSubTitle("子标题")
.build()
.show()
}
}
fun test(b: Int): () -> Int {
var a = 3
return fun(): Int {
a++
return a + b
}
}
}
......@@ -4,15 +4,29 @@ import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import com.qimai.android.widgetlib.toast.HolderContext
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
HolderContext.getInstance().register {
MainActivity@ this
}
findViewById<Button>(R.id.commonPop).setOnClickListener {
startActivity(Intent(MainActivity@ this, CommomPopActivity::class.java))
// ToastUtils.getInstance().showCenter("测试测试")
// ToastUtils.getInstance().setMsgColor(Color.RED).showTaost("测试出的的")
//
//// startActivity(Intent(MainActivity@ this, CommomPopActivity::class.java))
//// val loding = Loading()
//// loding.isCancelable =false
//// loding.show(supportFragmentManager, "loading")
TestWindow(this, findViewById<Button>(R.id.commonPop)).build().show()
}
findViewById<Button>(R.id.tip).setOnClickListener {
......
package com.qimai.android.widget
import android.content.Context
import android.graphics.Point
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import com.qimai.android.tools.dpTopx
import com.qimai.android.tools.getScreenWidth
import com.qimai.android.widgetlib.popup.AbsQMBasePopup
/**
* created by wangwei ON 2019-11-06 email:wangwei_5521@163.com
* @version 1.1.1
* @Description
**/
class TestWindow constructor(var ctx: Context, var anchor: View) : AbsQMBasePopup(ctx) {
override fun onShowEnd() {
dimBehind(0.5f)
}
override fun config(): WindowConfig {
return AbsQMBasePopup.WindowConfig().apply {
mContentView = LayoutInflater.from(ctx).inflate(R.layout.qm_alert_content, null)
mAnchorView = anchor
mWindowWidth = getScreenWidth(ctx) - ctx.dpTopx(30.0f)
mGravity = Gravity.CENTER
}
}
override fun onShowBegin(contentView: View): Point {
return Point(0, 0)
}
}
\ No newline at end of file
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.3.41'
repositories {
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }// 阿里云镜像
......
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
compile "androidx.core:core-ktx:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
mavenCentral()
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package com.qmai.android.fragmentcontroller;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.qmai.android.fragmentcontroller.test", appContext.getPackageName());
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.qmai.android.fragmentcontroller" />
package com.qmai.android.fragmentcontroller
import android.content.Context
import android.os.Bundle
import android.util.AttributeSet
import android.view.View
import androidx.fragment.app.Fragment
/**
* created by wangwei ON 2019-10-29 email:wangwei_5521@163.com
* @version 1.1.1
* @Description
**/
class LifeFragment :Fragment(){
override fun onAttach(context: Context) {
super.onAttach(context)
}
override fun onDestroy() {
super.onDestroy()
}
override fun onInflate(context: Context, attrs: AttributeSet, savedInstanceState: Bundle?) {
super.onInflate(context, attrs, savedInstanceState)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}
}
\ No newline at end of file
<resources>
<string name="app_name">FragmentController</string>
</resources>
package com.qmai.android.fragmentcontroller;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
include ':app', ':widgetlib', ':tools'
include ':app', ':widgetlib', ':tools', ':fragmentcontroller'
......@@ -42,7 +42,7 @@ repositories {
group 'com.qmai.android.tools'
version '1.1.6-SNAPSHOT'
version '1.1.7-SNAPSHOT'
gradlePublish {
......
package com.qimai.android.tools
import java.text.SimpleDateFormat
import java.util.*
/**
* created by wangwei ON 2019-09-23 email:wangwei_5521@163.com
* @version 1.1.1
* @Description
**/
fun formatCurrentDate(pattern: String) = getTimeFormatStr(pattern)
private fun getTimeFormatStr(pattern: String, original: Any = Date()) =
SimpleDateFormat(pattern, Locale.getDefault()).format(original)
?: ""
/**获取当前年份**/
val currentYear: Int
get() = Calendar.getInstance().get(Calendar.YEAR)
/**获取当前月份**/
val currentMonth
get() = Calendar.getInstance().get(Calendar.MONTH) + 1
/**获取当前日**/
val currentDay: Int
get() = Calendar.getInstance().get(Calendar.DATE)
/**获取当前时间戳**/
val currentTimeMillis: Long
get() = System.currentTimeMillis()
````
class TestWindow constructor(var ctx: Context, var anchor: View) : AbsQMBasePopup(ctx) {
override fun onShowEnd() {
dimBehind(0.5f)
}
override fun config(): WindowConfig {
return AbsQMBasePopup.WindowConfig().apply {
mContentView = LayoutInflater.from(ctx).inflate(R.layout.qm_alert_content, null)
mAnchorView = anchor
mWindowWidth = getScreenWidth(ctx) - ctx.dpTopx(30.0f)
mGravity = Gravity.CENTER
}
}
override fun onShowBegin(contentView: View): Point {
return Point(0, 0)
}
}
````
` 显示 TestWindow(this, anchorView).build().show()
`
\ No newline at end of file
......@@ -37,6 +37,8 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation project(path: ':tools')
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.github.ybq:Android-SpinKit:1.4.0'
// implementation 'com.qmai.android.tools:tools:1.1.1-SNAPSHOT'
}
repositories {
......@@ -45,7 +47,7 @@ repositories {
group 'com.qmai.android.sdk'
version '1.1.12-SNAPSHOT'
version '1.1.18-SNAPSHOT'
gradlePublish {
......
package com.qimai.android.span;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.CharacterStyle;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.LeadingMarginSpan;
import android.text.style.LineHeightSpan;
import android.text.style.MaskFilterSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.ReplacementSpan;
import android.text.style.ScaleXSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SubscriptSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.text.style.UpdateAppearance;
import android.util.Log;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.qimai.android.widgetlib.toast.HolderContext;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
/**
* created by wangwei ON 2019-11-04 email:wangwei_5521@163.com
*
* @version 1.1.1
* @Description
**/
public class SpanUtils {
private static final int COLOR_DEFAULT = 0xFEFFFFFF;
public static final int ALIGN_BOTTOM = 0;
public static final int ALIGN_BASELINE = 1;
public static final int ALIGN_CENTER = 2;
public static final int ALIGN_TOP = 3;
@IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_TOP})
@Retention(RetentionPolicy.SOURCE)
public @interface Align {
}
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private TextView mTextView;
private CharSequence mText;
private int flag;
private int foregroundColor;
private int backgroundColor;
private int lineHeight;
private int alignLine;
private int quoteColor;
private int stripeWidth;
private int quoteGapWidth;
private int first;
private int rest;
private int bulletColor;
private int bulletRadius;
private int bulletGapWidth;
private int fontSize;
private boolean fontSizeIsDp;
private float proportion;
private float xProportion;
private boolean isStrikethrough;
private boolean isUnderline;
private boolean isSuperscript;
private boolean isSubscript;
private boolean isBold;
private boolean isItalic;
private boolean isBoldItalic;
private String fontFamily;
private Typeface typeface;
private Layout.Alignment alignment;
private int verticalAlign;
private ClickableSpan clickSpan;
private String url;
private float blurRadius;
private BlurMaskFilter.Blur style;
private Shader shader;
private float shadowRadius;
private float shadowDx;
private float shadowDy;
private int shadowColor;
private Object[] spans;
private Bitmap imageBitmap;
private Drawable imageDrawable;
private Uri imageUri;
private int imageResourceId;
private int alignImage;
private int spaceSize;
private int spaceColor;
private SerializableSpannableStringBuilder mBuilder;
private int mType;
private final int mTypeCharSequence = 0;
private final int mTypeImage = 1;
private final int mTypeSpace = 2;
private SpanUtils(TextView textView) {
this();
mTextView = textView;
}
public SpanUtils() {
mBuilder = new SerializableSpannableStringBuilder();
mText = "";
mType = -1;
setDefault();
}
private void setDefault() {
flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
foregroundColor = COLOR_DEFAULT;
backgroundColor = COLOR_DEFAULT;
lineHeight = -1;
quoteColor = COLOR_DEFAULT;
first = -1;
bulletColor = COLOR_DEFAULT;
fontSize = -1;
proportion = -1;
xProportion = -1;
isStrikethrough = false;
isUnderline = false;
isSuperscript = false;
isSubscript = false;
isBold = false;
isItalic = false;
isBoldItalic = false;
fontFamily = null;
typeface = null;
alignment = null;
verticalAlign = -1;
clickSpan = null;
url = null;
blurRadius = -1;
shader = null;
shadowRadius = -1;
spans = null;
imageBitmap = null;
imageDrawable = null;
imageUri = null;
imageResourceId = -1;
spaceSize = -1;
}
/**
* Set the span of flag.
*
* @param flag The flag.
* <ul>
* <li>{@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}</li>
* <li>{@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}</li>
* <li>{@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}</li>
* <li>{@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}</li>
* </ul>
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setFlag(final int flag) {
this.flag = flag;
return this;
}
/**
* Set the span of foreground's color.
*
* @param color The color of foreground
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setForegroundColor(@ColorInt final int color) {
this.foregroundColor = color;
return this;
}
/**
* Set the span of background's color.
*
* @param color The color of background
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setBackgroundColor(@ColorInt final int color) {
this.backgroundColor = color;
return this;
}
/**
* Set the span of line height.
*
* @param lineHeight The line height, in pixel.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setLineHeight(@IntRange(from = 0) final int lineHeight) {
return setLineHeight(lineHeight, ALIGN_CENTER);
}
/**
* Set the span of line height.
*
* @param lineHeight The line height, in pixel.
* @param align The alignment.
* <ul>
* <li>{@link Align#ALIGN_TOP }</li>
* <li>{@link Align#ALIGN_CENTER}</li>
* <li>{@link Align#ALIGN_BOTTOM}</li>
* </ul>
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setLineHeight(@IntRange(from = 0) final int lineHeight,
@Align final int align) {
this.lineHeight = lineHeight;
this.alignLine = align;
return this;
}
/**
* Set the span of quote's color.
*
* @param color The color of quote
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setQuoteColor(@ColorInt final int color) {
return setQuoteColor(color, 2, 2);
}
/**
* Set the span of quote's color.
*
* @param color The color of quote.
* @param stripeWidth The width of stripe, in pixel.
* @param gapWidth The width of gap, in pixel.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setQuoteColor(@ColorInt final int color,
@IntRange(from = 1) final int stripeWidth,
@IntRange(from = 0) final int gapWidth) {
this.quoteColor = color;
this.stripeWidth = stripeWidth;
this.quoteGapWidth = gapWidth;
return this;
}
/**
* Set the span of leading margin.
*
* @param first The indent for the first line of the paragraph.
* @param rest The indent for the remaining lines of the paragraph.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setLeadingMargin(@IntRange(from = 0) final int first,
@IntRange(from = 0) final int rest) {
this.first = first;
this.rest = rest;
return this;
}
/**
* Set the span of bullet.
*
* @param gapWidth The width of gap, in pixel.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setBullet(@IntRange(from = 0) final int gapWidth) {
return setBullet(0, 3, gapWidth);
}
/**
* Set the span of bullet.
*
* @param color The color of bullet.
* @param radius The radius of bullet, in pixel.
* @param gapWidth The width of gap, in pixel.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setBullet(@ColorInt final int color,
@IntRange(from = 0) final int radius,
@IntRange(from = 0) final int gapWidth) {
this.bulletColor = color;
this.bulletRadius = radius;
this.bulletGapWidth = gapWidth;
return this;
}
/**
* Set the span of font's size.
*
* @param size The size of font.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setFontSize(@IntRange(from = 0) final int size) {
return setFontSize(size, false);
}
/**
* Set the span of size of font.
*
* @param size The size of font.
* @param isSp True to use sp, false to use pixel.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setFontSize(@IntRange(from = 0) final int size, final boolean isSp) {
this.fontSize = size;
this.fontSizeIsDp = isSp;
return this;
}
/**
* Set the span of proportion of font.
*
* @param proportion The proportion of font.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setFontProportion(final float proportion) {
this.proportion = proportion;
return this;
}
/**
* Set the span of transverse proportion of font.
*
* @param proportion The transverse proportion of font.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setFontXProportion(final float proportion) {
this.xProportion = proportion;
return this;
}
/**
* Set the span of strikethrough.
*
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setStrikethrough() {
this.isStrikethrough = true;
return this;
}
/**
* Set the span of underline.
*
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setUnderline() {
this.isUnderline = true;
return this;
}
/**
* Set the span of superscript.
*
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setSuperscript() {
this.isSuperscript = true;
return this;
}
/**
* Set the span of subscript.
*
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setSubscript() {
this.isSubscript = true;
return this;
}
/**
* Set the span of bold.
*
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setBold() {
isBold = true;
return this;
}
/**
* Set the span of italic.
*
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setItalic() {
isItalic = true;
return this;
}
/**
* Set the span of bold italic.
*
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setBoldItalic() {
isBoldItalic = true;
return this;
}
/**
* Set the span of font family.
*
* @param fontFamily The font family.
* <ul>
* <li>monospace</li>
* <li>serif</li>
* <li>sans-serif</li>
* </ul>
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setFontFamily(@NonNull final String fontFamily) {
this.fontFamily = fontFamily;
return this;
}
/**
* Set the span of typeface.
*
* @param typeface The typeface.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setTypeface(@NonNull final Typeface typeface) {
this.typeface = typeface;
return this;
}
/**
* Set the span of horizontal alignment.
*
* @param alignment The alignment.
* <ul>
* <li>{@link android.text.Layout.Alignment#ALIGN_NORMAL }</li>
* <li>{@link android.text.Layout.Alignment#ALIGN_OPPOSITE}</li>
* <li>{@link android.text.Layout.Alignment#ALIGN_CENTER }</li>
* </ul>
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setHorizontalAlign(@NonNull final Layout.Alignment alignment) {
this.alignment = alignment;
return this;
}
/**
* Set the span of vertical alignment.
*
* @param align The alignment.
* <ul>
* <li>{@link Align#ALIGN_TOP }</li>
* <li>{@link Align#ALIGN_CENTER }</li>
* <li>{@link Align#ALIGN_BASELINE}</li>
* <li>{@link Align#ALIGN_BOTTOM }</li>
* </ul>
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setVerticalAlign(@Align final int align) {
this.verticalAlign = align;
return this;
}
/**
* Set the span of click.
* <p>Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}</p>
*
* @param clickSpan The span of click.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setClickSpan(@NonNull final ClickableSpan clickSpan) {
if (mTextView != null && mTextView.getMovementMethod() == null) {
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
}
this.clickSpan = clickSpan;
return this;
}
/**
* Set the span of url.
* <p>Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}</p>
*
* @param url The url.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setUrl(@NonNull final String url) {
if (mTextView != null && mTextView.getMovementMethod() == null) {
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
}
this.url = url;
return this;
}
/**
* Set the span of blur.
*
* @param radius The radius of blur.
* @param style The style.
* <ul>
* <li>{@link android.graphics.BlurMaskFilter.Blur#NORMAL}</li>
* <li>{@link android.graphics.BlurMaskFilter.Blur#SOLID}</li>
* <li>{@link android.graphics.BlurMaskFilter.Blur#OUTER}</li>
* <li>{@link android.graphics.BlurMaskFilter.Blur#INNER}</li>
* </ul>
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setBlur(@FloatRange(from = 0, fromInclusive = false) final float radius,
final android.graphics.BlurMaskFilter.Blur style) {
this.blurRadius = radius;
this.style = style;
return this;
}
/**
* Set the span of shader.
*
* @param shader The shader.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setShader(@NonNull final Shader shader) {
this.shader = shader;
return this;
}
/**
* Set the span of shadow.
*
* @param radius The radius of shadow.
* @param dx X-axis offset, in pixel.
* @param dy Y-axis offset, in pixel.
* @param shadowColor The color of shadow.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setShadow(@FloatRange(from = 0, fromInclusive = false) final float radius,
final float dx,
final float dy,
final int shadowColor) {
this.shadowRadius = radius;
this.shadowDx = dx;
this.shadowDy = dy;
this.shadowColor = shadowColor;
return this;
}
/**
* Set the spans.
*
* @param spans The spans.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils setSpans(@NonNull final Object... spans) {
if (spans.length > 0) {
this.spans = spans;
}
return this;
}
/**
* Append the text text.
*
* @param text The text.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils append(@NonNull final CharSequence text) {
apply(mTypeCharSequence);
mText = text;
return this;
}
/**
* Append one line.
*
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendLine() {
apply(mTypeCharSequence);
mText = LINE_SEPARATOR;
return this;
}
/**
* Append text and one line.
*
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendLine(@NonNull final CharSequence text) {
apply(mTypeCharSequence);
mText = text + LINE_SEPARATOR;
return this;
}
/**
* Append one image.
*
* @param bitmap The bitmap of image.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendImage(@NonNull final Bitmap bitmap) {
return appendImage(bitmap, ALIGN_BOTTOM);
}
/**
* Append one image.
*
* @param bitmap The bitmap.
* @param align The alignment.
* <ul>
* <li>{@link Align#ALIGN_TOP }</li>
* <li>{@link Align#ALIGN_CENTER }</li>
* <li>{@link Align#ALIGN_BASELINE}</li>
* <li>{@link Align#ALIGN_BOTTOM }</li>
* </ul>
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendImage(@NonNull final Bitmap bitmap, @Align final int align) {
apply(mTypeImage);
this.imageBitmap = bitmap;
this.alignImage = align;
return this;
}
/**
* Append one image.
*
* @param drawable The drawable of image.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendImage(@NonNull final Drawable drawable) {
return appendImage(drawable, ALIGN_BOTTOM);
}
/**
* Append one image.
*
* @param drawable The drawable of image.
* @param align The alignment.
* <ul>
* <li>{@link Align#ALIGN_TOP }</li>
* <li>{@link Align#ALIGN_CENTER }</li>
* <li>{@link Align#ALIGN_BASELINE}</li>
* <li>{@link Align#ALIGN_BOTTOM }</li>
* </ul>
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendImage(@NonNull final Drawable drawable, @Align final int align) {
apply(mTypeImage);
this.imageDrawable = drawable;
this.alignImage = align;
return this;
}
/**
* Append one image.
*
* @param uri The uri of image.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendImage(@NonNull final Uri uri) {
return appendImage(uri, ALIGN_BOTTOM);
}
/**
* Append one image.
*
* @param uri The uri of image.
* @param align The alignment.
* <ul>
* <li>{@link Align#ALIGN_TOP }</li>
* <li>{@link Align#ALIGN_CENTER }</li>
* <li>{@link Align#ALIGN_BASELINE}</li>
* <li>{@link Align#ALIGN_BOTTOM }</li>
* </ul>
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendImage(@NonNull final Uri uri, @Align final int align) {
apply(mTypeImage);
this.imageUri = uri;
this.alignImage = align;
return this;
}
/**
* Append one image.
*
* @param resourceId The resource id of image.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendImage(@DrawableRes final int resourceId) {
return appendImage(resourceId, ALIGN_BOTTOM);
}
/**
* Append one image.
*
* @param resourceId The resource id of image.
* @param align The alignment.
* <ul>
* <li>{@link Align#ALIGN_TOP }</li>
* <li>{@link Align#ALIGN_CENTER }</li>
* <li>{@link Align#ALIGN_BASELINE}</li>
* <li>{@link Align#ALIGN_BOTTOM }</li>
* </ul>
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendImage(@DrawableRes final int resourceId, @Align final int align) {
apply(mTypeImage);
this.imageResourceId = resourceId;
this.alignImage = align;
return this;
}
/**
* Append space.
*
* @param size The size of space.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendSpace(@IntRange(from = 0) final int size) {
return appendSpace(size, Color.TRANSPARENT);
}
/**
* Append space.
*
* @param size The size of space.
* @param color The color of space.
* @return the single {@link SpanUtils} instance
*/
public SpanUtils appendSpace(@IntRange(from = 0) final int size, @ColorInt final int color) {
apply(mTypeSpace);
spaceSize = size;
spaceColor = color;
return this;
}
private void apply(final int type) {
applyLast();
mType = type;
}
public SpannableStringBuilder get() {
return mBuilder;
}
/**
* Create the span string.
*
* @return the span string
*/
public SpannableStringBuilder create() {
applyLast();
if (mTextView != null) {
mTextView.setText(mBuilder);
}
return mBuilder;
}
private void applyLast() {
if (mType == mTypeCharSequence) {
updateCharCharSequence();
} else if (mType == mTypeImage) {
updateImage();
} else if (mType == mTypeSpace) {
updateSpace();
}
setDefault();
}
private void updateCharCharSequence() {
if (mText.length() == 0) return;
int start = mBuilder.length();
if (start == 0 && lineHeight != -1) {// bug of LineHeightSpan when first line
mBuilder.append(Character.toString((char) 2))
.append("\n")
.setSpan(new AbsoluteSizeSpan(0), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
start = 2;
}
mBuilder.append(mText);
int end = mBuilder.length();
if (verticalAlign != -1) {
mBuilder.setSpan(new VerticalAlignSpan(verticalAlign), start, end, flag);
}
if (foregroundColor != COLOR_DEFAULT) {
mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag);
}
if (backgroundColor != COLOR_DEFAULT) {
mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag);
}
if (first != -1) {
mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag);
}
if (quoteColor != COLOR_DEFAULT) {
mBuilder.setSpan(
new CustomQuoteSpan(quoteColor, stripeWidth, quoteGapWidth),
start,
end,
flag
);
}
if (bulletColor != COLOR_DEFAULT) {
mBuilder.setSpan(
new CustomBulletSpan(bulletColor, bulletRadius, bulletGapWidth),
start,
end,
flag
);
}
if (fontSize != -1) {
mBuilder.setSpan(new AbsoluteSizeSpan(fontSize, fontSizeIsDp), start, end, flag);
}
if (proportion != -1) {
mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag);
}
if (xProportion != -1) {
mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag);
}
if (lineHeight != -1) {
mBuilder.setSpan(new CustomLineHeightSpan(lineHeight, alignLine), start, end, flag);
}
if (isStrikethrough) {
mBuilder.setSpan(new StrikethroughSpan(), start, end, flag);
}
if (isUnderline) {
mBuilder.setSpan(new UnderlineSpan(), start, end, flag);
}
if (isSuperscript) {
mBuilder.setSpan(new SuperscriptSpan(), start, end, flag);
}
if (isSubscript) {
mBuilder.setSpan(new SubscriptSpan(), start, end, flag);
}
if (isBold) {
mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag);
}
if (isItalic) {
mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag);
}
if (isBoldItalic) {
mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag);
}
if (fontFamily != null) {
mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag);
}
if (typeface != null) {
mBuilder.setSpan(new CustomTypefaceSpan(typeface), start, end, flag);
}
if (alignment != null) {
mBuilder.setSpan(new AlignmentSpan.Standard(alignment), start, end, flag);
}
if (clickSpan != null) {
mBuilder.setSpan(clickSpan, start, end, flag);
}
if (url != null) {
mBuilder.setSpan(new URLSpan(url), start, end, flag);
}
if (blurRadius != -1) {
mBuilder.setSpan(
new MaskFilterSpan(new BlurMaskFilter(blurRadius, style)),
start,
end,
flag
);
}
if (shader != null) {
mBuilder.setSpan(new ShaderSpan(shader), start, end, flag);
}
if (shadowRadius != -1) {
mBuilder.setSpan(
new ShadowSpan(shadowRadius, shadowDx, shadowDy, shadowColor),
start,
end,
flag
);
}
if (spans != null) {
for (Object span : spans) {
mBuilder.setSpan(span, start, end, flag);
}
}
}
private void updateImage() {
int start = mBuilder.length();
mText = "<img>";
updateCharCharSequence();
int end = mBuilder.length();
if (imageBitmap != null) {
mBuilder.setSpan(new CustomImageSpan(imageBitmap, alignImage), start, end, flag);
} else if (imageDrawable != null) {
mBuilder.setSpan(new CustomImageSpan(imageDrawable, alignImage), start, end, flag);
} else if (imageUri != null) {
mBuilder.setSpan(new CustomImageSpan(imageUri, alignImage), start, end, flag);
} else if (imageResourceId != -1) {
mBuilder.setSpan(new CustomImageSpan(imageResourceId, alignImage), start, end, flag);
}
}
private void updateSpace() {
int start = mBuilder.length();
mText = "< >";
updateCharCharSequence();
int end = mBuilder.length();
mBuilder.setSpan(new SpaceSpan(spaceSize, spaceColor), start, end, flag);
}
static class VerticalAlignSpan extends ReplacementSpan {
static final int ALIGN_CENTER = 2;
static final int ALIGN_TOP = 3;
final int mVerticalAlignment;
VerticalAlignSpan(int verticalAlignment) {
mVerticalAlignment = verticalAlignment;
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {
text = text.subSequence(start, end);
return (int) paint.measureText(text.toString());
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
text = text.subSequence(start, end);
Paint.FontMetricsInt fm = paint.getFontMetricsInt();
// int need = height - (v + fm.descent - fm.ascent - spanstartv);
// if (need > 0) {
// if (mVerticalAlignment == ALIGN_TOP) {
// fm.descent += need;
// } else if (mVerticalAlignment == ALIGN_CENTER) {
// fm.descent += need / 2;
// fm.ascent -= need / 2;
// } else {
// fm.ascent -= need;
// }
// }
// need = height - (v + fm.bottom - fm.top - spanstartv);
// if (need > 0) {
// if (mVerticalAlignment == ALIGN_TOP) {
// fm.bottom += need;
// } else if (mVerticalAlignment == ALIGN_CENTER) {
// fm.bottom += need / 2;
// fm.top -= need / 2;
// } else {
// fm.top -= need;
// }
// }
canvas.drawText(text.toString(), x, y - ((y + fm.descent + y + fm.ascent) / 2 - (bottom + top) / 2), paint);
}
}
static class CustomLineHeightSpan implements LineHeightSpan {
private final int height;
static final int ALIGN_CENTER = 2;
static final int ALIGN_TOP = 3;
final int mVerticalAlignment;
static Paint.FontMetricsInt sfm;
CustomLineHeightSpan(int height, int verticalAlignment) {
this.height = height;
mVerticalAlignment = verticalAlignment;
}
@Override
public void chooseHeight(final CharSequence text, final int start, final int end,
final int spanstartv, final int v, final Paint.FontMetricsInt fm) {
if (sfm == null) {
sfm = new Paint.FontMetricsInt();
sfm.top = fm.top;
sfm.ascent = fm.ascent;
sfm.descent = fm.descent;
sfm.bottom = fm.bottom;
sfm.leading = fm.leading;
} else {
fm.top = sfm.top;
fm.ascent = sfm.ascent;
fm.descent = sfm.descent;
fm.bottom = sfm.bottom;
fm.leading = sfm.leading;
}
int need = height - (v + fm.descent - fm.ascent - spanstartv);
if (need > 0) {
if (mVerticalAlignment == ALIGN_TOP) {
fm.descent += need;
} else if (mVerticalAlignment == ALIGN_CENTER) {
fm.descent += need / 2;
fm.ascent -= need / 2;
} else {
fm.ascent -= need;
}
}
need = height - (v + fm.bottom - fm.top - spanstartv);
if (need > 0) {
if (mVerticalAlignment == ALIGN_TOP) {
fm.bottom += need;
} else if (mVerticalAlignment == ALIGN_CENTER) {
fm.bottom += need / 2;
fm.top -= need / 2;
} else {
fm.top -= need;
}
}
if (end == ((Spanned) text).getSpanEnd(this)) {
sfm = null;
}
}
}
static class SpaceSpan extends ReplacementSpan {
private final int width;
private final Paint paint = new Paint();
private SpaceSpan(final int width) {
this(width, Color.TRANSPARENT);
}
private SpaceSpan(final int width, final int color) {
super();
this.width = width;
paint.setColor(color);
paint.setStyle(Paint.Style.FILL);
}
@Override
public int getSize(@NonNull final Paint paint, final CharSequence text,
@IntRange(from = 0) final int start,
@IntRange(from = 0) final int end,
@Nullable final Paint.FontMetricsInt fm) {
return width;
}
@Override
public void draw(@NonNull final Canvas canvas, final CharSequence text,
@IntRange(from = 0) final int start,
@IntRange(from = 0) final int end,
final float x, final int top, final int y, final int bottom,
@NonNull final Paint paint) {
canvas.drawRect(x, top, x + width, bottom, this.paint);
}
}
static class CustomQuoteSpan implements LeadingMarginSpan {
private final int color;
private final int stripeWidth;
private final int gapWidth;
private CustomQuoteSpan(final int color, final int stripeWidth, final int gapWidth) {
super();
this.color = color;
this.stripeWidth = stripeWidth;
this.gapWidth = gapWidth;
}
public int getLeadingMargin(final boolean first) {
return stripeWidth + gapWidth;
}
public void drawLeadingMargin(final Canvas c, final Paint p, final int x, final int dir,
final int top, final int baseline, final int bottom,
final CharSequence text, final int start, final int end,
final boolean first, final Layout layout) {
Paint.Style style = p.getStyle();
int color = p.getColor();
p.setStyle(Paint.Style.FILL);
p.setColor(this.color);
c.drawRect(x, top, x + dir * stripeWidth, bottom, p);
p.setStyle(style);
p.setColor(color);
}
}
static class CustomBulletSpan implements LeadingMarginSpan {
private final int color;
private final int radius;
private final int gapWidth;
private Path sBulletPath = null;
private CustomBulletSpan(final int color, final int radius, final int gapWidth) {
this.color = color;
this.radius = radius;
this.gapWidth = gapWidth;
}
public int getLeadingMargin(final boolean first) {
return 2 * radius + gapWidth;
}
public void drawLeadingMargin(final Canvas c, final Paint p, final int x, final int dir,
final int top, final int baseline, final int bottom,
final CharSequence text, final int start, final int end,
final boolean first, final Layout l) {
if (((Spanned) text).getSpanStart(this) == start) {
Paint.Style style = p.getStyle();
int oldColor = 0;
oldColor = p.getColor();
p.setColor(color);
p.setStyle(Paint.Style.FILL);
if (c.isHardwareAccelerated()) {
if (sBulletPath == null) {
sBulletPath = new Path();
// Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
sBulletPath.addCircle(0.0f, 0.0f, radius, Path.Direction.CW);
}
c.save();
c.translate(x + dir * radius, (top + bottom) / 2.0f);
c.drawPath(sBulletPath, p);
c.restore();
} else {
c.drawCircle(x + dir * radius, (top + bottom) / 2.0f, radius, p);
}
p.setColor(oldColor);
p.setStyle(style);
}
}
}
@SuppressLint("ParcelCreator")
static class CustomTypefaceSpan extends TypefaceSpan {
private final Typeface newType;
private CustomTypefaceSpan(final Typeface type) {
super("");
newType = type;
}
@Override
public void updateDrawState(final TextPaint textPaint) {
apply(textPaint, newType);
}
@Override
public void updateMeasureState(final TextPaint paint) {
apply(paint, newType);
}
private void apply(final Paint paint, final Typeface tf) {
int oldStyle;
Typeface old = paint.getTypeface();
if (old == null) {
oldStyle = 0;
} else {
oldStyle = old.getStyle();
}
int fake = oldStyle & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.getShader();
paint.setTypeface(tf);
}
}
static class CustomImageSpan extends CustomDynamicDrawableSpan {
private Drawable mDrawable;
private Uri mContentUri;
private int mResourceId;
private CustomImageSpan(final Bitmap b, final int verticalAlignment) {
super(verticalAlignment);
mDrawable = new BitmapDrawable(HolderContext.getContext().getResources(), b);
mDrawable.setBounds(
0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()
);
}
private CustomImageSpan(final Drawable d, final int verticalAlignment) {
super(verticalAlignment);
mDrawable = d;
mDrawable.setBounds(
0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()
);
}
private CustomImageSpan(final Uri uri, final int verticalAlignment) {
super(verticalAlignment);
mContentUri = uri;
}
private CustomImageSpan(@DrawableRes final int resourceId, final int verticalAlignment) {
super(verticalAlignment);
mResourceId = resourceId;
}
@Override
public Drawable getDrawable() {
Drawable drawable = null;
if (mDrawable != null) {
drawable = mDrawable;
} else if (mContentUri != null) {
Bitmap bitmap;
try {
InputStream is =
HolderContext.getContext().getContentResolver().openInputStream(mContentUri);
bitmap = BitmapFactory.decodeStream(is);
drawable = new BitmapDrawable(HolderContext.getContext().getResources(), bitmap);
drawable.setBounds(
0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()
);
if (is != null) {
is.close();
}
} catch (Exception e) {
Log.e("sms", "Failed to loaded content " + mContentUri, e);
}
} else {
try {
drawable = ContextCompat.getDrawable(HolderContext.getContext(), mResourceId);
drawable.setBounds(
0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()
);
} catch (Exception e) {
Log.e("sms", "Unable to find resource: " + mResourceId);
}
}
return drawable;
}
}
static abstract class CustomDynamicDrawableSpan extends ReplacementSpan {
static final int ALIGN_BOTTOM = 0;
static final int ALIGN_BASELINE = 1;
static final int ALIGN_CENTER = 2;
static final int ALIGN_TOP = 3;
final int mVerticalAlignment;
private CustomDynamicDrawableSpan() {
mVerticalAlignment = ALIGN_BOTTOM;
}
private CustomDynamicDrawableSpan(final int verticalAlignment) {
mVerticalAlignment = verticalAlignment;
}
public abstract Drawable getDrawable();
@Override
public int getSize(@NonNull final Paint paint, final CharSequence text,
final int start, final int end, final Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
if (fm != null) {
int lineHeight = fm.bottom - fm.top;
if (lineHeight < rect.height()) {
if (mVerticalAlignment == ALIGN_TOP) {
fm.top = fm.top;
fm.bottom = rect.height() + fm.top;
} else if (mVerticalAlignment == ALIGN_CENTER) {
fm.top = -rect.height() / 2 - lineHeight / 4;
fm.bottom = rect.height() / 2 - lineHeight / 4;
} else {
fm.top = -rect.height() + fm.bottom;
fm.bottom = fm.bottom;
}
fm.ascent = fm.top;
fm.descent = fm.bottom;
}
}
return rect.right;
}
@Override
public void draw(@NonNull final Canvas canvas, final CharSequence text,
final int start, final int end, final float x,
final int top, final int y, final int bottom, @NonNull final Paint paint) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
canvas.save();
float transY;
int lineHeight = bottom - top;
if (rect.height() < lineHeight) {
if (mVerticalAlignment == ALIGN_TOP) {
transY = top;
} else if (mVerticalAlignment == ALIGN_CENTER) {
transY = (bottom + top - rect.height()) / 2;
} else if (mVerticalAlignment == ALIGN_BASELINE) {
transY = y - rect.height();
} else {
transY = bottom - rect.height();
}
canvas.translate(x, transY);
} else {
canvas.translate(x, top);
}
d.draw(canvas);
canvas.restore();
}
private Drawable getCachedDrawable() {
WeakReference<Drawable> wr = mDrawableRef;
Drawable d = null;
if (wr != null) {
d = wr.get();
}
if (d == null) {
d = getDrawable();
mDrawableRef = new WeakReference<>(d);
}
return d;
}
private WeakReference<Drawable> mDrawableRef;
}
static class ShaderSpan extends CharacterStyle implements UpdateAppearance {
private Shader mShader;
private ShaderSpan(final Shader shader) {
this.mShader = shader;
}
@Override
public void updateDrawState(final TextPaint tp) {
tp.setShader(mShader);
}
}
static class ShadowSpan extends CharacterStyle implements UpdateAppearance {
private float radius;
private float dx, dy;
private int shadowColor;
private ShadowSpan(final float radius,
final float dx,
final float dy,
final int shadowColor) {
this.radius = radius;
this.dx = dx;
this.dy = dy;
this.shadowColor = shadowColor;
}
@Override
public void updateDrawState(final TextPaint tp) {
tp.setShadowLayer(radius, dx, dy, shadowColor);
}
}
private static class SerializableSpannableStringBuilder extends SpannableStringBuilder
implements Serializable {
private static final long serialVersionUID = 4909567650765875771L;
}
///////////////////////////////////////////////////////////////////////////
// static
///////////////////////////////////////////////////////////////////////////
public static SpanUtils with(final TextView textView) {
return new SpanUtils(textView);
}
}
package com.qimai.android.widgetlib;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
/**
* created by wangwei ON 2019-11-04 email:wangwei_5521@163.com
*
* @version 1.1.1
* @Description
**/
public class SectionItemView extends RelativeLayout {
public static final int TYPE_ARROW = 0;
public static final int TYPE_CHECK = 1;
public static final int TYPE_RADIO = 2;
public SectionItemView(Context context) {
this(context, null);
}
public SectionItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SectionItemView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(
/** layout 布局配置**/
attrs,
R.styleable.SectionItemView,
/** 通过attr 设置**/
defStyleAttr,
/** 默认样式 **/
0
);
int type = a.getInteger(R.styleable.SectionItemView_type, 0);
switch (type) {
case TYPE_ARROW: {
}
case TYPE_CHECK: {
}
case TYPE_RADIO: {
}
}
a.recycle();
}
}
package com.qimai.android.widgetlib.popup
import android.content.Context
import android.graphics.Color
import android.graphics.Point
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import android.view.*
import android.widget.FrameLayout
import android.widget.PopupWindow
/**
* created by wangwei ON 2019-08-22 email:wangwei_5521@163.com
* @version 1.1.1
* @Description
**/
abstract class AbsQMBasePopup(open var context: Context) {
private var mWindow = PopupWindow()
private lateinit var mRootViewWrapper: FrameLayout
private lateinit var mWindowManager: WindowManager
protected open var mDismissListener: PopupWindow.OnDismissListener? = null
abstract fun config(): WindowConfig
var touchOutside = true
set(value) {
field = value
mWindow.isOutsideTouchable = value
}
fun isShowing(): Boolean {
return mWindow.isShowing
}
fun build(): AbsQMBasePopup {
val config = config()
setContentView(contentView = config.mContentView)
mWindow.width = config.mWindowWidth
mWindow.height = ViewGroup.LayoutParams.WRAP_CONTENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
mWindow.isAttachedInDecor = false
}
if (config.mBackground == null) {
mWindow.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
} else {
mWindow.setBackgroundDrawable(config.mBackground)
}
mWindow.isTouchable = true
mWindow.isFocusable = true
mWindow.isOutsideTouchable = config.isOutsideTouchable
// 在相关的View被移除时,window也自动移除。避免当Fragment退出后,Fragment中弹出的PopupWindow还存在于界面上。
config.mAnchorView.addOnAttachStateChangeListener(object :
View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
}
override fun onViewDetachedFromWindow(v: View) {
if (isShowing()) {
dismiss()
}
}
})
return this
}
fun dimBehind(dim: Float) {
if (!::mWindowManager.isInitialized)
mWindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
if (!isShowing()) {
throw RuntimeException("should call after method show() or in onShowEnd()")
}
val decorView = getDecorView()
if (decorView != null) {
val p = decorView.layoutParams as WindowManager.LayoutParams
p.flags = p.flags or WindowManager.LayoutParams.FLAG_DIM_BEHIND
p.dimAmount = dim
mWindowManager.updateViewLayout(decorView, p)
}
}
protected abstract fun onShowBegin(contentView: View): Point
protected abstract fun onShowEnd()
fun dismiss() {
if (isShowing())
mWindow.dismiss()
}
fun show() {
if (!isShowing()) {
val point = onShowBegin(config().mContentView)
mWindow.showAtLocation(config().mAnchorView, config().mGravity, point.x, point.y)
onShowEnd()
}
}
fun setContentView(contentView: View) {
mRootViewWrapper = FrameLayout(contentView.context)
mRootViewWrapper.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
mRootViewWrapper.addView(contentView)
mWindow.contentView = mRootViewWrapper
mWindow.setTouchInterceptor(View.OnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_OUTSIDE) {
mWindow.dismiss()
return@OnTouchListener false
}
false
})
mWindow.setOnDismissListener {
this@AbsQMBasePopup.onDismiss()
if (mDismissListener != null) {
mDismissListener!!.onDismiss()
}
}
}
protected fun onDismiss() {}
private fun getDecorView(): View? {
var decorView: View? = null
try {
decorView = if (mWindow.background == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mWindow.contentView.parent as View
} else {
mWindow.contentView
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mWindow.contentView.parent.parent as View
} else {
mWindow.contentView.parent as View
}
}
} catch (ignore: Exception) {
}
return decorView
}
class WindowConfig {
var mWindowWidth = ViewGroup.LayoutParams.MATCH_PARENT
var mWindowHeight = ViewGroup.LayoutParams.WRAP_CONTENT
var mAnimationStyle: Int = -1
var mGravity: Int = Gravity.CENTER
var mBackground: Drawable? = null
lateinit var mContentView: View
lateinit var mAnchorView: View
var isOutsideTouchable = true
}
}
\ No newline at end of file
......@@ -4,7 +4,6 @@ import android.content.Context
import android.graphics.Point
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.PopupWindow
import com.qimai.android.tools.dpTopx
import com.qimai.android.tools.getScreenWidth
......@@ -17,7 +16,7 @@ import com.qimai.android.tools.getScreenWidth
class CommonPopWindow private constructor(
override var context: Context,
private val contentView: View,
private var animationStyle: Int,
private var animationStyles: Int,
override var gravity: Int,
override var mDismissListener: PopupWindow.OnDismissListener?
......@@ -28,13 +27,20 @@ class CommonPopWindow private constructor(
private constructor(builder: Builder) : this(
context = builder.mContentView.context,
contentView = builder.mContentView,
animationStyle = builder.animationStyle,
animationStyles = builder.animationStyle,
gravity = builder.gravity,
mDismissListener = builder.dismissListener
) {
this.setContentView(contentView)
this.setAnimatonStyle(animationStyle)
setWindowWidth(getScreenWidth(contentView.context) - context.dpTopx(30.0f))
this.setAnimationStyle(animationStyles)
if (builder.windowWidth < 0)
setWindowWidth(getScreenWidth(contentView.context) - context.dpTopx(30.0f))
else
setWindowWidth(builder.windowWidth)
if (builder.windowHeight > 0) {
setWindowHeight(builder.windowHeight)
}
}
......@@ -48,14 +54,22 @@ class CommonPopWindow private constructor(
internal var dismissListener: PopupWindow.OnDismissListener? = null
internal var windowWidth = ViewGroup.LayoutParams.MATCH_PARENT
internal var windowHeight = ViewGroup.LayoutParams.WRAP_CONTENT
var windowWidth = -1
var windowHeight = -1
fun configContentView(contentView: View) = apply {
this.mContentView = contentView
}
fun configWidowWith(windowWidth: Int) = apply {
this.windowWidth = windowWidth
}
fun configWidowHeight(windowHeight: Int) = apply {
this.windowHeight = windowHeight
}
fun configGravity(gravity: Int) = apply {
this.gravity = gravity
......
package com.qimai.android.widgetlib.popup
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.qimai.android.widgetlib.R
import kotlinx.android.synthetic.main.loading.view.*
/**
* created by wangwei ON 2019-10-21 email:wangwei_5521@163.com
* @version 1.1.1
* @Description
**/
class Loading : androidx.fragment.app.DialogFragment() {
private lateinit var rootView: View
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
val root = inflater.inflate(R.layout.loading, container, false)
rootView = root
return root
}
fun changeLoadText(loadingText: String) {
rootView.text.text = loadingText
}
fun show(manager: androidx.fragment.app.FragmentManager) {
show(manager, "loading")
}
}
\ No newline at end of file
......@@ -39,7 +39,7 @@ abstract class QMBasePopup(open var context: Context) {
this.mWindowHeight = windowHeight
}
fun setAnimatonStyle(style: Int) {
fun setAnimationStyle(style: Int) {
mWindow.animationStyle = style
}
......
package com.qimai.android.widgetlib.popup
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.Gravity
import android.view.LayoutInflater
......@@ -25,10 +23,11 @@ class QmAlertDialog private constructor(
private val subTitle: CharSequence,
private val leftButtonText: String,
private val rightButtonText: String,
private val leftButtonClickListener: (CommonPopWindow:CommonPopWindow) -> Unit,
private val rightButtonClickListener: (CommonPopWindow:CommonPopWindow) -> Unit,
private val leftButtonClickListener: (CommonPopWindow: CommonPopWindow) -> Unit,
private val rightButtonClickListener: (CommonPopWindow: CommonPopWindow) -> Unit,
private val rootView: ViewGroup,
private val dismissListener: PopupWindow.OnDismissListener?
, private val width: Int
) {
private lateinit var commonPopWindow: CommonPopWindow
......@@ -41,7 +40,8 @@ class QmAlertDialog private constructor(
leftButtonClickListener = builder.leftButtonClick,
rightButtonClickListener = builder.rightButtonClick,
rootView = builder.rootView,
dismissListener = builder.dismissListener
dismissListener = builder.dismissListener,
width = builder.width
)
fun show() {
......@@ -76,13 +76,17 @@ class QmAlertDialog private constructor(
this.rightButtonClickListener.invoke(commonPopWindow)
}
commonPopWindow = CommonPopWindow.Builder()
val builder = CommonPopWindow.Builder()
.configGravity(Gravity.CENTER)
.configContentView(contentView)
.dismissListener(PopupWindow.OnDismissListener {
dismissListener?.onDismiss()
})
.build()
if (width >0){
builder.configWidowWith(width)
}
commonPopWindow = builder.build()
}
commonPopWindow.show(rootView)
commonPopWindow.dimBehind(0.5f)
......@@ -104,11 +108,12 @@ class QmAlertDialog private constructor(
internal var leftButtonText = "取消"
internal var rightButtonText = "确定"
internal lateinit var leftButtonClick: (CommonPopWindow:CommonPopWindow) -> Unit
internal lateinit var rightButtonClick: (CommonPopWindow:CommonPopWindow) -> Unit
internal lateinit var leftButtonClick: (CommonPopWindow: CommonPopWindow) -> Unit
internal lateinit var rightButtonClick: (CommonPopWindow: CommonPopWindow) -> Unit
private lateinit var context: Context
internal lateinit var rootView: ViewGroup
internal var dismissListener: PopupWindow.OnDismissListener? = null
internal var width: Int = 0
fun withTitle(title: CharSequence) = apply {
......@@ -132,13 +137,21 @@ class QmAlertDialog private constructor(
this.rootView = rootView
}
fun withWidth(windowWidth: Int) = apply {
this.width = windowWidth
}
fun build(): QmAlertDialog {
return QmAlertDialog(this)
}
fun withLeftButton(leftButtonText: String, clickListener: (CommonPopWindow:CommonPopWindow) -> Unit) =
fun withLeftButton(
leftButtonText: String,
clickListener: (CommonPopWindow: CommonPopWindow) -> Unit
) =
apply {
this.leftButtonText = leftButtonText
......@@ -146,7 +159,10 @@ class QmAlertDialog private constructor(
}
fun withRightButton(rightButtonText: String, clickListener: (CommonPopWindow:CommonPopWindow) -> Unit) = apply {
fun withRightButton(
rightButtonText: String,
clickListener: (CommonPopWindow: CommonPopWindow) -> Unit
) = apply {
this.rightButtonText = rightButtonText
this.rightButtonClick = clickListener
......
package com.qimai.android.widgetlib.toast;
import android.content.Context;
/**
* created by wangwei ON 2019-11-04 email:wangwei_5521@163.com
*
* @version 1.1.1
* @Description
**/
public class HolderContext {
private static volatile HolderContext instance;
public static HolderContext getInstance() {
if (instance == null) {
synchronized (HolderContext.class) {
if (instance == null) {
instance = new HolderContext();
}
}
}
return instance;
}
public static Context getContext() {
return getInstance().holder.getContext();
}
public Holder holder;
public void register(Holder holder) {
this.holder = holder;
}
public interface Holder {
Context getContext();
}
}
package com.qimai.android.widgetlib.toast;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
/**
* created by wangwei ON 2019-11-04 email:wangwei_5521@163.com
*
* @version 1.1.1
* @Description
**/
public class ToastUtils {
private static volatile ToastUtils instance;
private static final int COLOR_DEFAULT = 0xFEFFFFFF;
private static volatile Toast mToast;
private int sMsgTextSize = -1;
private int sMsgColor = COLOR_DEFAULT;
private int sGravity = -1;
private int sBgColor = COLOR_DEFAULT;
private int sBgResource = -1;
public static ToastUtils getInstance() {
if (instance == null) {
synchronized (ToastUtils.class) {
if (instance == null) {
instance = new ToastUtils();
}
}
}
return instance;
}
public void showCenter(String msg) {
getToast().setText(msg);
mToast.setGravity(Gravity.CENTER, 0, 0);
mToast.show();
}
public void showTaost(CharSequence msg) {
getToast();
View toastView = mToast.getView();
TextView message = toastView.findViewById(android.R.id.message);
message.setText(msg);
if (sMsgTextSize != -1) {
message.setTextSize(sMsgTextSize);
}
if (sMsgColor != COLOR_DEFAULT) {
message.setTextColor(sMsgColor);
}
if (sGravity != -1) {
mToast.setGravity(sGravity, 0, 0);
}
if (sBgResource != -1) {
toastView.setBackgroundResource(sBgResource);
} else if (sBgColor != COLOR_DEFAULT) {
Drawable background = toastView.getBackground();
if (background != null) {
background.setColorFilter(
new PorterDuffColorFilter(sBgColor, PorterDuff.Mode.SRC_IN)
);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
toastView.setBackground(new ColorDrawable(sBgColor));
} else {
toastView.setBackgroundDrawable(new ColorDrawable(sBgColor));
}
}
}
mToast.show();
}
public ToastUtils setsMsgTextSize(int msgTextSize) {
sMsgTextSize = msgTextSize;
return this;
}
public ToastUtils setMsgColor(int color) {
this.sMsgColor = color;
return this;
}
public ToastUtils setGravity(int gravity) {
this.sGravity = gravity;
return this;
}
public ToastUtils setBgColor(int color) {
this.sBgColor = color;
return this;
}
public ToastUtils setBgResouce(int bgResouce) {
this.sBgColor = bgResouce;
return this;
}
private static Toast getToast() {
if (mToast == null) {
synchronized (ToastUtils.class) {
if (mToast == null) {
mToast = Toast.makeText(HolderContext.getContext(), "", Toast.LENGTH_LONG);
}
}
}
return mToast;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:background="@drawable/shape_alert_bg"
android:layout_gravity="center"
android:padding="16dp"
android:layout_height="wrap_content">
<com.github.ybq.android.spinkit.SpinKitView
android:id="@+id/spin_kit"
style="@style/SpinKitView.Large.FadingCircle"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_gravity="center"
app:SpinKit_Color="#E2CE22" />
<TextView
android:id="@+id/text"
android:text="加载中..."
android:textColor="@color/color_222222"
android:textSize="15sp"
android:layout_marginTop="16dp"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
\ No newline at end of file
......@@ -36,4 +36,12 @@
<attr name="overlay_borderColor" format="color" />
<attr name="overlay_borderWidth" format="dimension" />
</declare-styleable>
<declare-styleable name="SectionItemView">
<attr name="type" format="enum">
<enum name="arrow" value="0" />
<enum name="check" value="1" />
<enum name="radio" value="2" />
</attr>
</declare-styleable>
</resources>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment