kotlin語法總結
自定義屬性與自定義style
假如我們想要自定義textview 的屬性
先在res/value 下創(chuàng)建文件 attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView">
<attr name="header" format="reference"/>
<attr name="headerHeight" format="dimension"/>
<attr name="headerVisibleHeight" format="dimension"/>
<attr name="age" >
<flag name="child" value="10"/>
<flag name="young" value="18"/>
<flag name="old" value="60"/>
</attr>
</declare-styleable>
</resources>
然后在xml根布局中添加命名空間 這里的MyTextView可以定義為任何名字xmlns:MyTextView="http://schemas.android.com/apk/res-auto"
接著就可以在具體的自定義view標簽中使用 MyTextView: 屬性名 = 具體的值
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:MyTextView="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.bhb.cutomview.view.View12
android:layout_width="wrap_content"
android:layout_height="wrap_content"
MyTextView:header = "@mipmap/mgirl"
MyTextView:headerVisibleHeight = "100dp"
MyTextView:headerHeight = "300dp"
MyTextView:age = "young"
/>
</LinearLayout>
接下來在代碼中 使用TypedArray獲取屬性的值
class View12: TextView {
constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){
// 傳入attr.xml 文件中定義的Styleable名稱
var typedArray = context?.obtainStyledAttributes(attrs , R.styleable.MyTextView)
// 根據(jù)屬性的不同format 調(diào)用TypedArray不同的函數(shù)獲取對應的值
var headerHeight = typedArray?.getDimension(R.styleable.MyTextView_headerHeight, -1f);
var age = typedArray?.getInt(R.styleable.MyTextView_age , -1)
//獲取完畢之后需要釋放資源
typedArray?.recycle()
setText("headerHeight: $headerHeight age: $age")
}
}
效果如圖所示
自定義屬性的format類型
轉(zhuǎn)自 https://blog.csdn.net/dj0379/article/details/49662161
1. reference:參考某一資源ID顶滩。
(1)屬性定義:
<declare-styleable name = "名稱">
<attr name = "background" format = "reference" />
</declare-styleable>
(2)屬性使用:
<ImageView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:background = "@drawable/圖片ID"
/>
2. color:顏色值定嗓。
(1)屬性定義:
<declare-styleable name = "名稱">
<attr name = "textColor" format = "color" />
</declare-styleable>
(2)屬性使用:
<TextView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:textColor = "#00FF00"
/>
3. boolean:布爾值蒿讥。
(1)屬性定義:
<declare-styleable name = "名稱">
<attr name = "focusable" format = "boolean" />
</declare-styleable>
(2)屬性使用:
<Button
android:layout_width = "42dip"
android:layout_height = "42dip"
android:focusable = "true"
/>
4. dimension:尺寸值囚聚。
(1)屬性定義:
<declare-styleable name = "名稱">
<attr name = "layout_width" format = "dimension" />
</declare-styleable>
(2)屬性使用:
<Button
android:layout_width = "42dip"
android:layout_height = "42dip"
/>
5. float:浮點值潮罪。
(1)屬性定義:
<declare-styleable name = "AlphaAnimation">
<attr name = "fromAlpha" format = "float" />
<attr name = "toAlpha" format = "float" />
</declare-styleable>
(2)屬性使用:
<alpha
android:fromAlpha = "1.0"
android:toAlpha = "0.7"
/>
6. integer:整型值螃壤。
(1)屬性定義:
<declare-styleable name = "AnimatedRotateDrawable">
<attr name = "visible" />
<attr name = "frameDuration" format="integer" />
<attr name = "framesCount" format="integer" />
<attr name = "pivotX" />
<attr name = "pivotY" />
<attr name = "drawable" />
</declare-styleable>
(2)屬性使用:
<animated-rotate
xmlns:android = "[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"
android:drawable = "@drawable/圖片ID"
android:pivotX = "50%"
android:pivotY = "50%"
android:framesCount = "12"
android:frameDuration = "100"
/>
7. string:字符串描扯。
(1)屬性定義:
<declare-styleable name = "MapView">
<attr name = "apiKey" format = "string" />
</declare-styleable>
(2)屬性使用:
<com.google.android.maps.MapView
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
android:apiKey = "0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g"
/>
8. fraction:百分數(shù)盯滚。
(1)屬性定義:
<declare-styleable name="RotateDrawable">
<attr name = "visible" />
<attr name = "fromDegrees" format = "float" />
<attr name = "toDegrees" format = "float" />
<attr name = "pivotX" format = "fraction" />
<attr name = "pivotY" format = "fraction" />
<attr name = "drawable" />
</declare-styleable>
(2)屬性使用:
<rotate
xmlns:android = "[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"
android:interpolator = "@anim/動畫ID"
android:fromDegrees = "0"
android:toDegrees = "360"
android:pivotX = "200%"
android:pivotY = "300%"
android:duration = "5000"
android:repeatMode = "restart"
android:repeatCount = "infinite"
/>
9. enum:枚舉值踢械。
(1)屬性定義:
<declare-styleable name="名稱">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
(2)屬性使用:
<LinearLayout
xmlns:android = "[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"
android:orientation = "vertical"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
>
</LinearLayout>
10. flag:位或運算。
(1)屬性定義:
<declare-styleable name="名稱">
<attr name="windowSoftInputMode">
<flag name = "stateUnspecified" value = "0" />
<flag name = "stateUnchanged" value = "1" />
<flag name = "stateHidden" value = "2" />
<flag name = "stateAlwaysHidden" value = "3" />
<flag name = "stateVisible" value = "4" />
<flag name = "stateAlwaysVisible" value = "5" />
<flag name = "adjustUnspecified" value = "0x00" />
<flag name = "adjustResize" value = "0x10" />
<flag name = "adjustPan" value = "0x20" />
<flag name = "adjustNothing" value = "0x30" />
</attr>
</declare-styleable>
(2)屬性使用:
<activity
android:name = ".StyleAndThemeActivity"
android:label = "@string/app_name"
android:windowSoftInputMode = "stateUnspecified | stateUnchanged | stateHidden">
<intent-filter>
<action android:name = "android.intent.action.MAIN" />
<category android:name = "android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
注意:
屬性定義時可以指定多種類型值魄藕。
(1)屬性定義:
<declare-styleable name = "名稱">
<attr name = "background" format = "reference|color" />
</declare-styleable>
(2)屬性使用:
<ImageView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:background = "@drawable/圖片ID|#00FF00"
/>
測量與布局
ViewGroup繪制流程
onMeasure 測量當前控件的大小
onLayuot 使用layout函數(shù)對所有子控件進行布局
onDraw 根據(jù)布局的位置繪圖
onMeasure函數(shù)與MeasureSpec
onMeasure 函數(shù)測量完成之后 要通過void setMeasuredDimension(int measuredWidth, int measuredHeight) 函數(shù)設置給系統(tǒng) 給onLayuot函數(shù)提供數(shù)值
MeasureSpec的組成
measuredWidth 和 measuredHeight 都是int 類型 在內(nèi)存中都是32位
前兩位代表mode 后30位代表數(shù)值
有三種模式
未指定 UNSPECIFIED = 0 << MODE_SHIFT; 子元素可以得到任意想要的大小
確切的值 EXACTLY = 1 << MODE_SHIFT; 子元素有確定的大小
最多 AT_MOST = 2 << MODE_SHIFT; 子元素可以得到最大的大小
其中MODE_SHIFT = 30
也就是三種模式的二進制值分別是 0 1 2 向左移30位 即
00 0000000000 0000000000 0000000000
01 0000000000 0000000000 0000000000
10 0000000000 0000000000 0000000000
模式提取
MeasureSpec 提供了函數(shù)獲取measuredWidth或者measuredHeight 的模式
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
二進制值為
11 0000000000 0000000000 0000000000
數(shù)值提取
MeasureSpec 也提供了函數(shù)
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
~MODE_MASK 二進制值為
00 1111111111 1111111111 1111111111
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
模式與數(shù)值主要用到了 與 非 運算
xml 中定義的大小 對應的模式
xml定義 | 對應的模式 | 描述 |
---|---|---|
wrap_content | AT_MOST | 最多得到父控件的大小 |
match_parent | EXACTLY | 得到的是父控件已經(jīng)確定的大小 |
具體的值 | EXACTLY | 指定的大小 |
UNSPECIFIED 基本用不到
onLayout 函數(shù)
void onLayout(boolean changed, int left, int top, int right, int bottom)
在ViewGroup中這是一個抽象函數(shù) 說明凡事派生自ViewGoup 的類都必須自己去實現(xiàn)這個函數(shù)
像 LinearLayout RelativeLayout 等布局都重寫了這個函數(shù) 然后在按照自己的規(guī)則對子視圖進行布局
示例 自定義LinearLayout
自定義 縱向顯示的LinearLayout
class View12LinearLayout : ViewGroup {
constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
var height = 0
var width = 0
for (index in 0 until childCount){
// 測量子控件
var child = getChildAt(index)
measureChild(child , widthMeasureSpec , heightMeasureSpec)
var childHeight = child.measuredHeight
var childWidth = child.measuredWidth
// 高度累加 寬度取最大值
height += childHeight
width = Math.max(childWidth , width)
}
// 告訴系統(tǒng)計算完的值
setMeasuredDimension( if(widthMode == MeasureSpec.EXACTLY) widthSize else width ,
if(heightMode == MeasureSpec.EXACTLY) heightSize else height)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
var top = 0
var count = childCount
for (index in 0 until count){
var child = getChildAt(index)
var childheight = child.measuredHeight
var childwidth = child.measuredWidth
// 遍歷每一個ziview 然后設置其布局位置
// 因為是縱向排列 x起點是0 y起點累加子view的高度
child.layout(0 ,top ,childwidth , top+childheight)
top += childheight
}
}
}
xml布局
<com.bhb.cutomview.view.View12LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text = "第一個view"
android:background="#ff0000"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text = "第二個view"
android:background="#ff00ff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text = "第三個view"
android:background="#ee3345"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.bhb.cutomview.view.View12LinearLayout>
獲取子view控件的 margin
現(xiàn)在我們把第二個view 設置margin值 運行起來之后發(fā)現(xiàn)并沒有效果
這是因為測量和布局都是我們自己實現(xiàn)的沒有根據(jù)margin布局
<TextView
android:text = "第二個view"
android:layout_marginTop="10dp"
android:layout_marginBottom="20dp"
android:background="#ff00ff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
接下來我們需要重寫三個函數(shù)
override fun generateLayoutParams(p: LayoutParams?): LayoutParams {
return MarginLayoutParams(p)
}
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context , attrs)
}
override fun generateDefaultLayoutParams(): LayoutParams {
return MarginLayoutParams(LayoutParams.MATCH_PARENT , LayoutParams.MATCH_PARENT)
}
然后修改 onMeasure 和 onLayout 的部分代碼
onMeasure 修改
for (index in 0 until childCount){
// 測量子控件
var child = getChildAt(index)
// 獲取margin
var lp = child.layoutParams as MarginLayoutParams
measureChild(child , widthMeasureSpec , heightMeasureSpec)
// 計算高度加上 margintop 和 marginbottom
var childHeight = child.measuredHeight + lp.topMargin + lp.bottomMargin
var childWidth = child.measuredWidth + lp.leftMargin + lp.rightMargin
// 高度累加 寬度取最大值
height += childHeight
width = Math.max(childWidth , width)
}
onLayout 修改
for (index in 0 until count){
var child = getChildAt(index)
// 獲取margin
var lp = child.layoutParams as MarginLayoutParams
var childheight = child.measuredHeight
var childwidth = child.measuredWidth + lp.leftMargin + lp.rightMargin
// 遍歷每一個ziview 然后設置其布局位置
// 因為是縱向排列 x起點是0 y起點累加子view的高度
// 布局位置 以及子view高度 根據(jù)margin計算
child.layout(0 ,top + lp.topMargin ,childwidth , top + lp.topMargin +childheight )
top += childheight + lp.topMargin + lp.bottomMargin
}
為什么要把 child.layoutParams 強轉(zhuǎn)成 MarginLayoutParams
首先container初始化子控件時内列, 會調(diào)用generateLayoutParams 函數(shù)來為子控件生成對應的布局屬性 但默認只生成 layout_width 和 layout_height , 即正常情況下調(diào)用generateLayoutParams函數(shù)生成的layoutparams 實例是不能獲取到 margin的
如果我們還需要與margin相關的參數(shù)就只能重寫generateLayoutParams函數(shù)
Viewgroup源碼
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public LayoutParams(Context c, AttributeSet attrs) {
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
setBaseAttributes(a,
R.styleable.ViewGroup_Layout_layout_width,
R.styleable.ViewGroup_Layout_layout_height);
a.recycle();
}
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
width = a.getLayoutDimension(widthAttr, "layout_width");
height = a.getLayoutDimension(heightAttr, "layout_height");
}
可以看出generateLayoutParams生成的layoutparams最終是由setBaseAttributes決定的
而setBaseAttributes函數(shù)值獲取了 layout_width 和 layout_height
MarginLayoutParams 源碼
public MarginLayoutParams(Context c, AttributeSet attrs) {
super();
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
setBaseAttributes(a,
R.styleable.ViewGroup_MarginLayout_layout_width,
R.styleable.ViewGroup_MarginLayout_layout_height);
int margin = a.getDimensionPixelSize(
com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
if (margin >= 0) {
leftMargin = margin;
topMargin = margin;
rightMargin= margin;
bottomMargin = margin;
} else {
int horizontalMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
int verticalMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);
if (horizontalMargin >= 0) {
leftMargin = horizontalMargin;
rightMargin = horizontalMargin;
} else {
leftMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
UNDEFINED_MARGIN);
if (leftMargin == UNDEFINED_MARGIN) {
mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
leftMargin = DEFAULT_MARGIN_RESOLVED;
}
rightMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginRight,
UNDEFINED_MARGIN);
if (rightMargin == UNDEFINED_MARGIN) {
mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
rightMargin = DEFAULT_MARGIN_RESOLVED;
}
}
startMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginStart,
DEFAULT_MARGIN_RELATIVE);
endMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
DEFAULT_MARGIN_RELATIVE);
if (verticalMargin >= 0) {
topMargin = verticalMargin;
bottomMargin = verticalMargin;
} else {
topMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginTop,
DEFAULT_MARGIN_RESOLVED);
bottomMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
DEFAULT_MARGIN_RESOLVED);
}
if (isMarginRelative()) {
mMarginFlags |= NEED_RESOLUTION_MASK;
}
}
final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
}
// Layout direction is LTR by default
mMarginFlags |= LAYOUT_DIRECTION_LTR;
a.recycle();
}
可以看見 MarginLayoutParams獲取了margin 屬性 并且優(yōu)先獲取margin 其次才是 marginleft marginright等屬性
實現(xiàn)FlowLayout 布局
給子view設置通用的style style.xml文件下添加代碼
<style name="text_flag_01">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_margin">10dp</item>
<item name="android:background">#ff0000</item>
<item name="android:textColor">#ffffff</item>
</style>
布局文件代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:MyTextView="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.bhb.cutomview.view.View12FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text = "第一個view"
style="@style/text_flag_01"/>
<TextView
android:text = "第二個view"
style="@style/text_flag_01"/>
<TextView
android:text = "第三個view"
style="@style/text_flag_01"/>
<TextView
android:text = "第四個view"
style="@style/text_flag_01"/>
<TextView
android:text = "第五個view"
style="@style/text_flag_01"/>
<TextView
android:text = "第六個view"
style="@style/text_flag_01"/>
<TextView
android:text = "第七個view"
style="@style/text_flag_01"/>
<TextView
android:text = "第八個view"
style="@style/text_flag_01"/>
<TextView
android:text = "第九個view"
style="@style/text_flag_01"/>
</com.bhb.cutomview.view.View12FlowLayout>
</LinearLayout>
自定義view 代碼
package com.bhb.cutomview.view
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
/**
* create by BHB on 7/10/21
* 自定義 FlowLayout 布局
*
*/
//在xml標簽上添加margin屬性 發(fā)現(xiàn)運行之后沒有效果 這是因為測量和布局都是我們自己實現(xiàn)的沒有根據(jù)margin布局
class View12FlowLayout : ViewGroup {
constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){
}
override fun generateLayoutParams(p: LayoutParams?): LayoutParams {
return MarginLayoutParams(p)
}
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context , attrs)
}
override fun generateDefaultLayoutParams(): LayoutParams {
return MarginLayoutParams(LayoutParams.MATCH_PARENT , LayoutParams.MATCH_PARENT)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
var lineWidth = 0 // 當前行的寬度
var lineHeight = 0 // 當前行的高度
var width = 0 // 控件總寬度
var height = 0 //控件總高度
for(i in 0 until childCount){
var child = getChildAt(i)
// 先測量子view 才能獲取寬度 高度
measureChild(child , widthMeasureSpec , heightMeasureSpec)
var lp = child.layoutParams as MarginLayoutParams
var childWidth = child.measuredWidth + lp.leftMargin + lp.rightMargin
var childHeight = child.measuredHeight + lp.topMargin + lp.bottomMargin
// 繪制第一個view 的時候 height 和lineheight 的值就是 子view的高度
if(i == 0){
height = childHeight
lineHeight = childHeight
}
if(lineWidth + childWidth > widthSize){
//需要換行
width = Math.max(lineWidth , width)
height+= lineHeight
//換行之后 當前行的寬高 就是當前子view的寬高
lineHeight += childHeight
lineWidth = childWidth
}else{
//在當前行繼續(xù)排列
lineHeight = Math.max(lineHeight , childHeight)
lineWidth += childWidth
}
setMeasuredDimension( if(widthMode == MeasureSpec.EXACTLY) widthSize else width ,
if(heightMode == MeasureSpec.EXACTLY) heightSize else height)
}
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
var top = 0
var left = 0
var lineWith = 0
var lineHeight = 0
var count = childCount
for (index in 0 until count){
var child = getChildAt(index)
// 獲取margin
var lp = child.layoutParams as MarginLayoutParams
var childheight = child.measuredHeight + lp.topMargin + lp.bottomMargin
var childwidth = child.measuredWidth + lp.leftMargin + lp.rightMargin
if(childwidth + lineWith > measuredWidth){
top += lineHeight
left = 0
lineHeight = childheight
lineWith = childwidth
}else{
lineHeight = Math.max(lineHeight , childheight)
lineWith += childwidth
}
//計算childview 的left top right bottom
var lc = left + lp.leftMargin
var tc = top + lp.topMargin
var rc = lc + child.measuredWidth
var bc = tc + child.measuredHeight
child.layout(lc, tc , rc , bc )
//將left 置為下一個子控件的起點
left += childwidth
}
}
}
顯示效果
GestureDetector 手勢檢測
我們知道 View類有一個 onTouchListener 內(nèi)部接口 通過重寫它的 onTouch函數(shù)可以處理一些touch事件
但是這個函數(shù)太過簡單 如果需要處理一些復雜的手勢 使用這個接口就會很麻煩
android 給我們提供了GestureDetector 這個類可以識別很多手勢 在識別出手勢之后 具體的業(yè)務邏輯由開發(fā)者自己實現(xiàn)
class MainActivity13 : AppCompatActivity(), View.OnTouchListener{
lateinit var gestureDetector :GestureDetector
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main13)
// 設置監(jiān)聽 構造函數(shù)有多個
gestureDetector = GestureDetector(GestureListener())
gestureDetector.setOnDoubleTapListener(DoubleTapListener())
// GestureDetector 還有一個監(jiān)聽函數(shù)
gestureDetector = GestureDetector(object : GestureDetector.SimpleOnGestureListener() {
//這里包含了GestureListener 和 DoubleTapListener 的所有函數(shù)
// 需要就進行重寫 不需要則不處理
override fun onDown(e: MotionEvent?): Boolean {
return super.onDown(e)
}
override fun onDoubleTap(e: MotionEvent?): Boolean {
return super.onDoubleTap(e)
}
// 判斷用戶是左滑 還是 右滑
// 判斷標準 用戶向左滑動距離超100像素 且移動速度超過200像素/秒 即認為是左滑 右滑同理
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
if(e1 == null || e2 == null){
return super.onFling(e1, e2, velocityX, velocityY)
}
Log.e("view13" , "velocityX ===== $velocityX")
if( (e1.x - e2.x )> 100 && Math.abs(Math.abs(velocityX)) > 200 ){
Log.e("view13" , "fling left")
}else if((e2.x - e1.x )> 100 && Math.abs(Math.abs(velocityX)) > 200 ){
Log.e("view13" , "fling right")
}
return true
}
})
textview.setOnTouchListener(this)
textview.isFocusable = true
textview.isClickable = true
textview.isLongClickable = true
}
// 將事件交給GestureDetector 處理
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
return gestureDetector.onTouchEvent(event)
}
class GestureListener : GestureDetector.OnGestureListener{
override fun onShowPress(e: MotionEvent?) {
// 按壓事件 且按下的時間超過瞬間
Log.e("view13", "onShowPress")
}
override fun onLongPress(e: MotionEvent?) {
// 長按事件
Log.e("view13", "onLongPress")
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
// 一次單獨的輕擊抬起事件
Log.e("view13", "onSingleTapUp")
return true
}
override fun onDown(e: MotionEvent?): Boolean {
// 按下屏幕就會觸發(fā)
Log.e("view13", "onDown")
return true
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
// 滑屏事件 用戶按下 滑動后在松開
Log.e("view13", "onFling")
return true
}
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent?,
distanceX: Float,
distanceY: Float
): Boolean {
// 滑動事件 在屏幕上拖動控件或者以拋的動作 都會多次觸發(fā)
Log.e("view13", "onScroll")
return true
}
}
class DoubleTapListener : GestureDetector.OnDoubleTapListener{
override fun onDoubleTap(e: MotionEvent?): Boolean {
// 雙擊事件
Log.e("view13", "onDoubleTap")
return true
}
override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
// 雙擊間隔中發(fā)生的事件 包含 down up move
Log.e("view13", "onDoubleTapEvent")
return true
}
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
// 用于判斷是否是單擊還是雙擊事件
Log.e("view13", "onSingleTapConfirmed")
return true }
}
}
Window 與 WindowManager
WindowManager 可通過getSystemService(Context.WINDOW_SERVICE)
WindowManager.addView(View view, ViewGroup.LayoutParams params) 可向界面添加view
WindowManager.LayoutParams 構造函數(shù)
public LayoutParams(int w, int h, int _type, int _flags, int _format)
type 用來表示 Window 的類型
window 有三種類型
應用window 1-99
子window 1000-1999
系統(tǒng)window 2000-2999
層級大的window會覆蓋在層級小的window上面
_flags 用來控制window 的顯示特性 常用的幾個選項
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 表示window區(qū)域內(nèi)的事件自己處理 區(qū)域外的時間傳遞給底層的window處理
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 表示window不需要獲取焦點 不接收各種輸入事件
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 讓window顯示在鎖屏上
// 不設置這個彈出框的透明遮罩顯示為黑色
params.format = PixelFormat.TRANSLUCENT;
class MainActivity13Windowmanager : AppCompatActivity(), View.OnClickListener,
View.OnTouchListener {
// 延遲初始化變量
lateinit var mImageView : ImageView
lateinit var mLayoutparams : WindowManager.LayoutParams
lateinit var mWindowManager : WindowManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main13windowmanager)
// 清單文件添加權限 與 申請權限
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
var intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
startActivityForResult(intent ,100)
}else{
init()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == 100){
init()
}
}
fun init(){
btn_add.setOnClickListener(this)
btn_remove.setOnClickListener(this)
mWindowManager = applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
}
override fun onClick(v: View?) {
when (v?.id){
R.id.btn_add ->{
// 添加控件
mImageView = ImageView(this)
mImageView.setBackgroundResource(R.mipmap.ic_launcher)
mLayoutparams = WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT ,WindowManager.LayoutParams.WRAP_CONTENT ,
2099 ,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED ,
PixelFormat.TRANSPARENT)
mLayoutparams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
// 設置初始位置
mLayoutparams.gravity = Gravity.TOP or Gravity.LEFT
mLayoutparams.x = 0
mLayoutparams.y = 300
mImageView.setOnTouchListener(this)
mWindowManager.addView(mImageView , mLayoutparams)
}
R.id.btn_remove ->{
// 移除控件
mImageView?.let {
mWindowManager.removeViewImmediate(mImageView)
}
}
}
}
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
//監(jiān)聽觸摸事件 讓添加的view 跟隨手指移動
event?.let {
mImageView?.let {
var rawX = event.rawX
var rawY = event.rawY
when (event.action){
MotionEvent.ACTION_MOVE -> {
mLayoutparams.x = rawX.toInt() - mImageView.width/2
mLayoutparams.y = rawY.toInt() - mImageView.height
mWindowManager.updateViewLayout(mImageView , mLayoutparams)
}
}
}
}
return false
}
}