Android滑動(dòng)沖突的完美解決方案
關(guān)于滑動(dòng)沖突
在Android開(kāi)發(fā)中,如果是一些簡(jiǎn)單的布局,都很容易搞定,但是一旦涉及到復(fù)雜的頁(yè)面,特別是為了兼容小屏手機(jī)而使用了ScrollView以后,就會(huì)出現(xiàn)很多點(diǎn)擊事件的沖突,最經(jīng)典的就是ScrollView中嵌套了ListView。我想大部分剛開(kāi)始接觸Android的同學(xué)們都踩到過(guò)這個(gè)坑,下面跟著小編一起來(lái)看看解決方案吧。。
同方向滑動(dòng)沖突
比如ScrollView嵌套ListView,或者是ScrollView嵌套自己
這里先看一張效果圖

上圖是在購(gòu)物軟件上常見(jiàn)的上拉查看圖文詳情,關(guān)于這中動(dòng)畫效果的實(shí)現(xiàn),其實(shí)實(shí)現(xiàn)整體的效果,辦法是有很多的,網(wǎng)上有很多相關(guān)的例子,但是對(duì)某些細(xì)節(jié)的處理不是很清晰,比如,下拉之后顯示的部分(例如底部的圖文詳情)又是一個(gè)類ScrollView的控件(比如WebView)的話,又會(huì)產(chǎn)生新的問(wèn)題。這里就以下拉查看圖文詳情為背景做一下同方向滑動(dòng)沖突的分析。
整體思路
這里看下圖

多個(gè)ScrollView嵌套示意圖
首先,關(guān)于這張圖做一些設(shè)定:
1.黑色的框代表手機(jī)屏幕
2.綠色的框代表一個(gè)外層的ScrollView
3.兩個(gè)紅色的框代表嵌套在里面的兩個(gè)類ScrollView控件,這里我們就暫時(shí)簡(jiǎn)稱為 SUp,SDown
好了,接下來(lái)就分析一下實(shí)現(xiàn)整個(gè)流程的過(guò)程。
這里必須明確的一點(diǎn),無(wú)論何時(shí),SUp和SDown可見(jiàn)的部分始終是手機(jī)屏幕的高度。知道了這一點(diǎn),我們就可以按以下步驟展開(kāi)
首先,我們確保外部的ScrollView不攔截滑動(dòng)事件,這樣SUp必然獲得此次事件,然后根據(jù)其Action_Move事件,當(dāng)其為向下滑動(dòng)且自身滑動(dòng)距離+屏幕高度=其自身高度 時(shí),即可認(rèn)為SUp滑動(dòng)到了底部,此時(shí)外部ScrollView可攔截滑動(dòng)事件,從而保證整個(gè)視圖能夠繼續(xù)向下滑動(dòng),這個(gè)時(shí)候底部SDown就顯示出來(lái)了。
同理,這時(shí)候不允許外部ScrollView攔截滑動(dòng)事件,由SDown處理,根據(jù)其Action_move事件,當(dāng)其為向上滑動(dòng),且自身可滑動(dòng)距離為0時(shí),就說(shuō)明SDown已經(jīng)滑動(dòng)到了頂部,這時(shí)外部ScrollView又可以獲得攔截滑動(dòng)事件的權(quán)利,從而保證整個(gè)視圖能夠向上繼續(xù)滑動(dòng),此時(shí)SUp再次顯示,又開(kāi)始新一輪循環(huán)攔截。
這樣整體的一個(gè)流程就可以實(shí)現(xiàn)動(dòng)圖中的效果。好了,說(shuō)完原理,還是看代碼。
代碼實(shí)現(xiàn)
SUp實(shí)現(xiàn)
public class UpScrollView extends ScrollView {
/**
* 屏幕高度
*/
private int mScreenHeight;
/**
* 上一次的坐標(biāo)
*/
private float mLastY;
/**
* 當(dāng)前View滑動(dòng)距離
*/
private int mScrollY;
/**
* 當(dāng)前View內(nèi)子控件高度
*/
private int mChildH;
public UpScrollView(Context context) {
super(context);
init(context);
}
public UpScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public UpScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
mScreenHeight = dm.heightPixels;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
//默認(rèn)設(shè)定頂層View不攔截
getParent().getParent().requestDisallowInterceptTouchEvent(true);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float y = ev.getY();
float deltaY = y - mLastY;
mChildH = this.getChildAt(0).getMeasuredHeight();
int translateY = mChildH - mScrollY;
//向上滑動(dòng)時(shí),如果translateY等于屏幕高度時(shí),即表明滑動(dòng)到底部,可又頂層View控制滑動(dòng)
if (deltaY < 0 && translateY == mScreenHeight) {
getParent().getParent().requestDisallowInterceptTouchEvent(false);
}
break;
default:
break;
}
return super.onTouchEvent(ev);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
mScrollY = t;
}
}
這里在ACTION_MOVE里做了減法,其實(shí)道理是一樣的。
onScrollChanged 是在View類中實(shí)現(xiàn),查看其API可以看到其第二個(gè)參數(shù)t解釋
@param t Current vertical scroll origin.
即為當(dāng)前View此次滑動(dòng)的距離
SDown實(shí)現(xiàn)
public class MyWebView extends WebView {
public float oldY;
private int t;
public MyWebView(Context context) {
super(context);
init();
}
public MyWebView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
WebSettings settings = getSettings();
settings.setJavaScriptEnabled(true);
setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
getParent().getParent().requestDisallowInterceptTouchEvent(true);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
oldY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float Y = ev.getY();
float Ys = Y - oldY;
if (Ys > 0 && t == 0) {
getParent().getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
return super.onTouchEvent(ev);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
this.t = t;
super.onScrollChanged(l, t, oldl, oldt);
}
}
以上看到,這里底部的View并沒(méi)有繼承ScrollView,而是選擇繼承了WebView,這里只是為了方便,當(dāng)然繼承ScrollView也是沒(méi)有問(wèn)題。這里只是需要按實(shí)際情況考慮,因?yàn)榈撞繄D文詳情的內(nèi)容就是一個(gè)WebView加載數(shù)據(jù)。
這個(gè)類的實(shí)現(xiàn),按照之前說(shuō)的原理應(yīng)該很好理解。
外部ScrollView
public class CustomerScrollViews extends ScrollView {
/**
* 屏幕高度
*/
private int mScreenHeight;
private UpScrollView upScrollView;
private MyWebView myWebView;
private boolean init = false;
private float fator = 0.2f;
private int factorHeight;
private boolean upShow = true;
public CustomerScrollViews(Context context) {
super(context);
init(context);
}
public CustomerScrollViews(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CustomerScrollViews(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
mScreenHeight = dm.heightPixels;
factorHeight = (int) (mScreenHeight * fator);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!init) {
LinearLayout parentView = (LinearLayout) getChildAt(0);
//獲得內(nèi)部的兩個(gè)子view
upScrollView = (UpScrollView) parentView.getChildAt(0);
myWebView = (MyWebView) parentView.getChildAt(2);
// //并設(shè)定其高度為屏幕高度
upScrollView.getLayoutParams().height = mScreenHeight;
myWebView.getLayoutParams().height = mScreenHeight;
init = true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
scrollTo(0, 0);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
int scrollY = getScrollY();
if (upShow) {
if (scrollY <= factorHeight) {
smoothScrollTo(0, 0);
} else {
smoothScrollTo(0, mScreenHeight);
upShow = false;
}
} else {
int scrollpadding = mScreenHeight - scrollY;
if (scrollpadding >= factorHeight) {
this.smoothScrollTo(0, 0);
upShow = true;
} else {
this.smoothScrollTo(0, mScreenHeight);
}
}
return true;
}
return super.onTouchEvent(ev);
}
}
這個(gè)類的實(shí)現(xiàn),就很靈活了,在onMeasure方法中初始化完內(nèi)部的View之后,在OnTouch方法中就可以根據(jù)實(shí)際需求完成不同的邏輯實(shí)現(xiàn),這里只是為了仿照查看圖文詳情的效果,對(duì)整個(gè)視圖通過(guò)ScrollView的smoothScrollTo方法進(jìn)行位移變化,這個(gè)邏輯很簡(jiǎn)單。
這里重點(diǎn)說(shuō)一下一個(gè)地方:
upScrollView = (UpScrollView) parentView.getChildAt(0); myWebView = (MyWebView) parentView.getChildAt(2);
你可能會(huì)奇怪中間的child(1)去了哪里?這里還要從MainActivity的布局文件說(shuō)起
dual_scrollview_activity_layout1.xml
<?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" android:orientation="vertical"> <com.example.dreamwork.activity.superscrollview.CustomerScrollViews android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <com.example.dreamwork.activity.superscrollview.UpScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@drawable/taobao" /> <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@drawable/taobao" /> <TextView android:textSize="20sp" android:padding="10dp" android:gravity="center" android:layout_marginTop="20dp" android:layout_marginBottom="60dp" android:text="查看圖文詳情" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </com.example.dreamwork.activity.superscrollview.UpScrollView> <include layout="@layout/selector_tab_items" /> <com.example.dreamwork.activity.superscrollview.MyWebView android:id="@+id/web" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> </com.example.dreamwork.activity.superscrollview.CustomerScrollViews> </LinearLayout>
整個(gè)布局文件可以看出,我們?cè)贑ustomerScrollViews這個(gè)最外層的自定義ScrollView內(nèi)部又放置了兩個(gè)自定義的ScrollView(就如我們看到的原理圖那樣),只不過(guò)在這兩個(gè)ScrollView類控件的中間通過(guò)layout又放置一個(gè)LinearLayout,里面的內(nèi)容就是在動(dòng)圖中看到的那個(gè)中間的寫著qq,baidu字樣的用于切換WebView內(nèi)容的一個(gè)View。這里就不貼代碼了。
這樣,你就可以理解之前的child(1)為什么被跳過(guò)了吧。
使用
public class DualScrollViewActivity1 extends Activity implements View.OnClickListener {
private MyWebView webView;
private TextView sinaTv, qqTv, baiduTv;
private View line1, line2, line3;
private final String BAIDU = "http://www.baidu.com";
private final String QQ = "http://www.qq.com";
private final String SINA = "http://sina.cn";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
InitView();
sinaTv.performClick();
}
private void InitView() {
setContentView(R.layout.dual_scrollview_activity_layout1);
webView = V.f(this, R.id.web);
sinaTv = V.f(this, R.id.one);
sinaTv.setOnClickListener(this);
qqTv = V.f(this, R.id.two);
qqTv.setOnClickListener(this);
baiduTv = V.f(this, R.id.three);
baiduTv.setOnClickListener(this);
line1 = V.f(this, R.id.line1);
line2 = V.f(this, R.id.line2);
line3 = V.f(this, R.id.line3);
}
@Override
public void onClick(View v) {
reset();
String url = "";
switch (v.getId()) {
case R.id.one:
line1.setVisibility(View.VISIBLE);
url = SINA;
break;
case R.id.two:
line2.setVisibility(View.VISIBLE);
url = QQ;
break;
case R.id.three:
line3.setVisibility(View.VISIBLE);
url = BAIDU;
break;
default:
break;
}
webView.loadUrl(url);
}
private void reset(){
line1.setVisibility(View.GONE);
line2.setVisibility(View.GONE);
line3.setVisibility(View.GONE);
}
}
關(guān)于底部View內(nèi)容更新,WebView 通過(guò)加載不同URL實(shí)現(xiàn)不同視圖效果,只是作為Demo測(cè)試,實(shí)際中應(yīng)考慮通過(guò)fragment切換實(shí)現(xiàn)。
這里對(duì)滑動(dòng)沖突的解決方法,是由內(nèi)而外的展開(kāi),默認(rèn)使頂層View失去攔截能力,在由底部View的滑動(dòng)距離,做出不同邏輯判斷控制了頂層的攔截與否;這也是比較容易理解和實(shí)現(xiàn)的思路。當(dāng)然,對(duì)于此類滑動(dòng)沖突,有很多不同思路,這里只是列舉其一。
實(shí)際開(kāi)發(fā)開(kāi)發(fā)中,這種帶有同方向滑動(dòng)特性的控件嵌套時(shí),產(chǎn)生的問(wèn)題不只是滑動(dòng)沖突,有時(shí)候會(huì)有內(nèi)容顯示不全或不顯示的情況。
最常見(jiàn)如ScrollView嵌套ListView,這種情況只需自定義ListView使其高度計(jì)算為一個(gè)很大的值,某種意義上讓其失去了滑動(dòng)的特性,但是這樣也讓ListView貌似失去了視圖回收機(jī)制,這種時(shí)候如果加載很多很多很多圖片,效果就會(huì)很不理想。對(duì)于這種情況,通過(guò)對(duì)ListView添加headView及footView也是一種解決的辦法,但也得實(shí)際UI情況允許。
ScrollView嵌套R(shí)ecyclerView時(shí)稍微麻煩一點(diǎn),需要自定義ScrollView,還需要自定義實(shí)現(xiàn)LinearLayoutManager。
不同方向滑動(dòng)沖突
比如ScrollView嵌套ViewPager,或者是ViewPager嵌套ScrollView,這種情況其實(shí)很典型?,F(xiàn)在大部分應(yīng)用最外層都是ViewPager+Fragment 的底部切換(比如微信)結(jié)構(gòu),這種時(shí)候,就很容易出現(xiàn)滑動(dòng)沖突。不過(guò)ViewPager里面無(wú)論是嵌套ListView還是ScrollView,滑動(dòng)沖突是沒(méi)有的,畢竟是官方的東西,可能已經(jīng)考慮到了這些,所以比較完善。
復(fù)雜一點(diǎn)的滑動(dòng)沖突,基本上就是這兩個(gè)沖突結(jié)合的結(jié)果。
滑動(dòng)沖突解決思路
滑動(dòng)沖突,就其本質(zhì)來(lái)說(shuō),兩個(gè)不同方向(或者是同方向)的View,其中有一個(gè)是占主導(dǎo)地位的,每次總是搶著去處理外界的滑動(dòng)行為,這樣就導(dǎo)致一種很別扭的用戶體驗(yàn),明明只是橫向的滑動(dòng)了一下,縱向的列表卻在垂直方向發(fā)生了動(dòng)作。就是說(shuō),這個(gè)占主導(dǎo)地位的View,每一次都身不由己的攔截了這個(gè)滑動(dòng)的動(dòng)作,因此,要解決滑動(dòng)沖突,就是得明確告訴這個(gè)占主導(dǎo)地位的View,什么時(shí)候你該攔截,什么時(shí)候你不應(yīng)該攔截,應(yīng)該由下一層的View去處理這個(gè)滑動(dòng)動(dòng)作。
這里不明白的同學(xué),可以去了解一下Android Touch事件的分發(fā)機(jī)制,這也是解決滑動(dòng)沖突的核心知識(shí)。
滑動(dòng)沖突
這里,說(shuō)一下背景情況。之前做下拉刷新、上拉加載更多時(shí)一直使用的是PullToRefreshView這個(gè)控件,因?yàn)楹芊奖?,不用?dǎo)入三方工程。在其內(nèi)部可以放置ListView,GridView及ScrollView,非常方便,用起來(lái)可謂是屢試不爽。但是直到有一天,因項(xiàng)目需要,在ListView頂部加了一個(gè)輪播圖控件BannerView(這個(gè)可以參考之前寫的一篇學(xué)習(xí)筆記)。結(jié)果發(fā)現(xiàn)輪播圖滑動(dòng)的時(shí)候,和縱向的下拉刷新組件沖突了。
如之前所說(shuō),解決滑動(dòng)沖突的關(guān)鍵,就是明確告知接收到Touch的View,是否需要攔截此次事件。
解決方法
解決方案1,從外部攔截機(jī)制考慮
這里,相當(dāng)于是PullToRefreshView嵌套了ViewPager,那么每次優(yōu)先接收到Touch事件的必然是PullToRefreshView。這樣就清楚了,看代碼:
在PullToRefreshView中:
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
int y = (int) e.getRawY();
int x = (int) e.getRawX();
boolean resume = false;
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
// 發(fā)生down事件時(shí),記錄y坐標(biāo)
mLastMotionY = y;
mLastMotionX = x;
resume = false;
break;
case MotionEvent.ACTION_MOVE:
// deltaY > 0 是向下運(yùn)動(dòng),< 0是向上運(yùn)動(dòng)
int deltaY = y - mLastMotionY;
int deleaX = x - mLastMotionX;
if (Math.abs(deleaX) > Math.abs(deltaY)) {
resume = false;
} else {
//當(dāng)前正處于滑動(dòng)
if (isRefreshViewScroll(deltaY)) {
resume = true;
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
break;
}
return resume;
}
這里最關(guān)鍵的代碼就是這行
if (Math.abs(deleaX) > Math.abs(deltaY)) {
resume = false;
}
橫向滑動(dòng)距離大于縱向時(shí),無(wú)須攔截這次滑動(dòng)事件。其實(shí),就是這么簡(jiǎn)單,但前提是你必須明確了解Android Touch事件的傳遞機(jī)制,期間各個(gè)方法執(zhí)行的順序及意義。
解決方案2,從內(nèi)容逆向思維分析
有時(shí)候,我們不想去修改引入的第三方控件,或者說(shuō)是無(wú)法修改時(shí)。就必須考慮從當(dāng)前從Touch傳遞事件中最后的那個(gè)View逆向考慮。首先,由Android中View的Touch事件傳遞機(jī)制,我們知道Touch事件,首先必然由最外層View攔截,如果無(wú)法更改這個(gè)最外層View,那么是不是就沒(méi)轍了呢?其實(shí)不然,Android這么高大上的系統(tǒng)必然考慮到了這個(gè)問(wèn)題,好了廢話不說(shuō),先看代碼
private BannerView carouselView;
private Context mContext;
private PullToRefreshView refreshView;
.........
refreshView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
carouselView.getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
});
carouselView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
carouselView.getParent().requestDisallowInterceptTouchEvent(true);
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
int deltaY = y - lastY;
int deltaX = x - lastX;
if (Math.abs(deltaX) < Math.abs(deltaY)) {
carouselView.getParent().requestDisallowInterceptTouchEvent(false);
} else {
carouselView.getParent().requestDisallowInterceptTouchEvent(true);
}
default:
break;
}
return false;
}
});
首先說(shuō)一下這個(gè)方法
public abstract void requestDisallowInterceptTouchEvent (boolean disallowIntercept)
API里的意思很明確,子View如果不希望其父View攔截Touch事件時(shí),可調(diào)用此方法。當(dāng)disallowIntercept這個(gè)參數(shù)為true時(shí),父View將不攔截。
PS:這個(gè)方法的命名和其參數(shù)的使用邏輯,讓我想到了一句很有意思的話,敵人的敵人就是朋友,真不知道Google的大神們?cè)趺聪氲模且阋粋€(gè)反邏輯。
言歸正傳。這里攔截直接也很明確,在carouselView的onTouch方法中每次進(jìn)入就設(shè)定父View不攔截此次事件,然后在MOTION_MOVE時(shí)候,根據(jù)滑動(dòng)的距離判斷再?zèng)Q定是父View是否有權(quán)利攔截Touch事件(即滑動(dòng)行為)。
總結(jié)
好了,本文內(nèi)容到這基本就結(jié)束了,本篇文章只是提供一種解決方法的思路,在具體的場(chǎng)景下,交互往往是貼合具體業(yè)務(wù)需求的。但是不管怎么樣,找出點(diǎn)擊事件截?cái)嗪吞幚淼臅r(shí)機(jī)是最重要的,圍繞這個(gè)關(guān)鍵點(diǎn),總能找出相應(yīng)的解決方法。
- Android中實(shí)現(xiàn)可滑動(dòng)的Tab的3種方式
- Android中實(shí)現(xiàn)監(jiān)聽(tīng)ScrollView滑動(dòng)事件
- android中使用Activity實(shí)現(xiàn)監(jiān)聽(tīng)手指上下左右滑動(dòng)
- Android中實(shí)現(xiàn)水平滑動(dòng)(橫向滑動(dòng))ListView示例
- android 通過(guò)向viewpage中添加listview來(lái)完成滑動(dòng)效果(類似于qq滑動(dòng)界面)
- Android仿今日頭條滑動(dòng)頁(yè)面導(dǎo)航效果
- Android 頂部標(biāo)題欄隨滑動(dòng)時(shí)的漸變隱藏和漸變顯示效果
- 解析Android中實(shí)現(xiàn)滑動(dòng)翻頁(yè)之ViewFlipper的使用詳解
- Android App中ViewPager所帶來(lái)的滑動(dòng)沖突問(wèn)題解決方法
- Android實(shí)現(xiàn)滑動(dòng)效果
相關(guān)文章
Android實(shí)現(xiàn)類似360,QQ管家那樣的懸浮窗
用到的就是WindowManager以及WindowManager.LayoutParams,對(duì)這個(gè)LayoutParams做文章,當(dāng)設(shè)置為屬性后,然后,創(chuàng)建一個(gè)View,將這個(gè)View添加到WindowManager中就行2013-06-06
Android超詳細(xì)講解組件LinearLayout的使用
LinearLayout又稱作線性布局,是一種非常常用的布局。正如它的名字所描述的一樣,這個(gè)布局會(huì)將它所包含的控件在線性方向上依次排列。既然是線性排列,肯定就不僅只有一個(gè)方向,這里一般只有兩個(gè)方向:水平方向和垂直方向2022-03-03
Flutter使用sqflite處理數(shù)據(jù)表變更的方法詳解
了解過(guò)數(shù)據(jù)庫(kù)的同學(xué)應(yīng)該會(huì)知道,數(shù)據(jù)表結(jié)構(gòu)是可能發(fā)生改變的。所以本文為大家介紹了Flutter?使用?sqflite?處理數(shù)據(jù)表變更的版本升級(jí)處理方法,感興趣的可以了解一下2023-04-04
Android?Activity?View加載與繪制流程深入刨析源碼
這篇文章主要介紹了Android?Activity?View的加載與繪制流程源碼分析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Android 保存Fragment 切換狀態(tài)實(shí)例代碼
本文主要介紹Android Fragment的應(yīng)用,這里給大家用實(shí)例代碼詳細(xì)介紹了Android Fragment 切換狀態(tài),有需要的小伙伴可以參考下2016-07-07
Android使用GridView實(shí)現(xiàn)日歷的簡(jiǎn)單功能
這篇文章主要為大家詳細(xì)介紹了Android使用GridView實(shí)現(xiàn)日歷的簡(jiǎn)單功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
Android超實(shí)用的Toast提示框優(yōu)化分享
Toast是Android中用來(lái)顯示顯示信息的一種機(jī)制,和Dialog不一樣的是,Toast是沒(méi)有焦點(diǎn)的,而且Toast顯示的時(shí)間有限,過(guò)一定的時(shí)間就會(huì)自動(dòng)消失。那么這篇文章跟大家分享下Android中Toast的優(yōu)化,對(duì)大家日常開(kāi)發(fā)還是很實(shí)用,下面來(lái)一起看看吧。2016-09-09

