編輯:關於Android編程
現在的一些購物類App例如淘寶,京東等,在物品詳情頁,都采用了類似分層的模式,即上拉加載詳情的方式,節省了空間,使用戶的體驗更加的舒適。只要對於某個東西的介紹很多時,都可以采取這樣的方式,第一個頁面顯示必要的,第二個頁面顯示詳細信息。
在之前寫項目的時候,曾經寫過一個類似淘寶詳情頁的UI效果,如下:

仔細分析這種UI效果,其實很簡單,就是兩個頁面,垂直擺放,同時兩個頁面之間過渡時,加上一層特殊處理,及當第二個頁面顯示多少時,松開手指時復原或者直接顯示第二個頁面。
根據這種特殊的UI效果進行實現封裝,最終的效果如下:

能夠實現頁面的切換,當滑動到第二個頁面不足顯示區域的三分之一時,則松開手指時會還原,如果超過三分之一,則會跳到第二個頁面。
同時實現了一些事件分發的處理,能夠嵌入按鈕,ViewPager等控件。
編寫xml文件,添加控件
在實現中,一定要將控件的高度設置為match_parent,因為在代碼中需要獲取顯示區域的高度,用以判斷是否復原和跳轉頁面。
在該控件中添加兩個布局控件。
在Activity中查找控件並設置一些監聽。
public class DetailVerticalActivity extends AppCompatActivity {
private DetailVerticalView v;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_taobao_detail);
// 查找控件
v = (DetailVerticalView) findViewById(R.id.detailVerticalView);
// 設置滾動監聽的回調
v.setScrollChangeListener(new DetailVerticalView.ScrollChangeListener() {
@Override
public void scrollY(int y) {
// y軸滑動偏移量的回調
}
@Override
public void onScollStateChange(int type) {
// 滑動到那個頁面狀體的變化
if(DetailVerticalView.TOP == type){
Toast.makeText(DetailVerticalActivity.this, "1", Toast.LENGTH_SHORT).show();
}else if(DetailVerticalView.BOTTOM==type){
Toast.makeText(DetailVerticalActivity.this, "2", Toast.LENGTH_SHORT).show();
}
}
});
}
public void test(View view){
Toast.makeText(getApplicationContext(),"點擊",Toast.LENGTH_SHORT).show();
}
}
在實現過程中,主要考慮以下幾點問題:
如何布置兩個頁面的控件位置。解決方式是使自定義View實現ViewGroup,重寫onMeasure和onLayout方法,對子View進行布局。
如何判斷兩個頁面何時跳轉,解決方式:獲取View顯示區域高度的三分之一,如果超過,則顯示第二個頁面,否則
則恢復原狀,不顯示第二個頁面。
解決方式:使用Scroller進行滑動的控制。
判斷的一些邊界的問題。滑動到底部和滑動到頂部時,等不可再滑,需要對邊界進行配置。
事件分發的處理。對於點擊等事件,利用onInterceptTouchEvent() 對相關事件進行攔截。
創建對象,初始化控件
/**
*
* 仿淘寶詳情頁
* Created by alex_mahao on 2016/8/29.
*/
public class DetailVerticalView extends ViewGroup {
// 滑動到頂部的狀態
public static final int TOP = 1;
// 滑動到底部的狀態
public static final int BOTTOM = 2;
public static final String TAG = "DetailVerticalView";
/**
* 屏幕高度
*/
private int mScreenHeight;
/**
* 手指上次觸摸事件的y軸位置
*/
private int mLastY;
/**
* 點擊時y軸的偏移量
*/
private int mScrollY;
/**
* 滾動控件
*/
private Scroller mScroller;
/**
* 最小移動距離判定
*/
private int mTouchSlop;
/**
* 滑動結束的偏移量
*/
private int mEnd;
/**
* 初始按下y軸坐標
*/
private int mDownStartY;
/**
* 記錄當前y軸坐標
*/
private int y;
/**
* 控件的高度
*/
private int mHeight;
/**
* 監聽的回調
*/
private ScrollChangeListener scrollChangeListener;
public DetailVerticalView(Context context) {
super(context, null);
}
public DetailVerticalView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* 初始化方法
*/
private void init() {
// 創建滑對象,以便滑動時使用
mScroller = new Scroller(getContext());
// 獲取系統的最小距離
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(ViewConfiguration.get(getContext()));
}
//....
}
一些變量的定義後面會有,此處不提。
mTouchSlop,系統默認的最小距離。當手指滑動的大小大於該值時,則認為是滑動,不在是點擊,後面會通過與該值比對進行事件攔截。
測量自身的大小和兩個頁面控件的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 獲取顯示區域的高度
mScreenHeight = MeasureSpec.getSize(heightMeasureSpec);
// 遍歷子View
int count = getChildCount();
// 控件的高度
mHeight = 0;
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
// 測量子View 高度
int childHeight = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
measureChild(childView, widthMeasureSpec, childHeight);
mHeight = getChildAt(i).getMeasuredHeight() + mHeight;
}
// 設置控件的高度
setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY));
}
設置自定義View的高度為match_parent,就是為了獲取顯示區域的高度,從此處可以看出。
測量子View的高度,便於後面的布局使用。在中間,會看到我對childView的高度設置了值childHeight,該值的目的是告訴子View,我給你一個很大的值,你看著自己需要多少自己設置就行,及AT_MOST模式
在最後的時候,設置當前控件的高度,為兩個頁面控件的高度之和。
對兩個頁面控件進行布置onLayout()方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int childHeight = 0;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
// 確定位置
child.layout(l, childHeight, r, childHeight + child.getMeasuredHeight());
childHeight = +child.getMeasuredHeight();
}
}
}
這一段沒什麼難度,看著理解即可。
處理事件分發
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 獲取當前觸摸位置Y軸坐標
y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 第一次按下時的Y軸坐標
mDownStartY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
// 如果大於mTouchSlop,認為是滑動,則不再讓子View處理事件
if (Math.abs(y - mDownStartY) > mTouchSlop) {
// 因為是在onTouchEvent中處理,如果子View處理過事件,
// 則該控件的onTouchEvent()不再有DOWN事件,在這裡獲取一些值
mLastY = y;
mScrollY = getScrollY();
return true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
對於事件的傳遞,流程為 父:onInterceptTouchEventr - > 子:onTouchEvent() -> 父:onTouchEvent(),當然這裡只是寫了必要的流程。如果子:onTouchEvent()返回true,則當前控件就無法捕獲觸摸事件,那麼滑動就無從處理了。所以,在此處,我們判斷垂直滑動大於最小滑動距離時,就把事件給截斷,不在交給子控件處理。
同時,如果子控件處理了一些事件,那麼父控件的onTouchEvent()中,將不在有DOWN事件,那麼我們需要先獲取一次值,在打斷子View的時候。
滑動相關的處理
@Override
public boolean onTouchEvent(MotionEvent event) {
y = (int) event.getY();
mScrollY = getScrollY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 如果通過事件攔截獲取到的觸摸,則直接就為Move方法
mLastY = y;
mScrollY = getScrollY();
break;
case MotionEvent.ACTION_MOVE:
int dy = mLastY - y;
Log.i(TAG,"dy:"+dy+"-"+"mLastY:"+mLastY+"-"+"mScrolly:"+mScrollY);
if(mScrollY+dy<0){
// 滑動到頂部,不可再滑動
scrollTo(0,0);
}else if(mScrollY+dy>getMeasuredHeight()-mScreenHeight){
//底部時
scrollTo(0,getMeasuredHeight()-mScreenHeight);
}else{
scrollBy(0, dy);
mLastY = y;
}
break;
case MotionEvent.ACTION_UP:
// 當前偏移量是
int absScroll = mScrollY+mScreenHeight-getChildAt(0).getMeasuredHeight();
if(absScroll<0||absScroll>mScreenHeight){
// 第一個頁面未滑到底部,不做操作,如果absScroll>屏幕的高度,則完全滾動
// 不做任何滾動操作
}else if(absScroll>mScreenHeight/3){
// 監聽的回調
if(scrollChangeListener!=null){
scrollChangeListener.onScollStateChange(BOTTOM);
}
// 松開時顯示第二個頁面
mScroller.startScroll(0, mScrollY, 0, mScreenHeight-absScroll);
}else if(absScroll<mscreenheight pre="" return="">
該段是整個自定義View中最重要的方法,總結來說干了兩件事情:
ACTION_MOVE中,處理觸摸滑動的事件,及手指在屏幕滑動時的相關處理。邊界處理,判斷滑動時,如果到頂部和底部,則不再滑動,否則進行相關的滑動。
ACTION_UP:手指離開屏幕時,對是否需要跳轉進行判斷,如果需要跳轉,則跳轉。在手指離開時,判斷當前頁面的顯示量,偏移量+顯示區域的高度-第一個控件的高度=第二個控件顯示的高度。
如果顯示的高度小於0,則表示還在第一個頁面,第二個頁面顯示都沒顯示,不做任何處理。
如果顯示的高度大於顯示區域的高度,則表示第二個頁面完全顯示了,不做任何處理。
否則,如果顯示的高度大於顯示區域的1/3,則跳轉到第二個頁面,小於,則恢復到第一個頁面。
可以看到通過mScroller.startScroll()實現頁面的滑動跳轉,使用該方式,需要重寫另一個方法
@Override
public void computeScroll() {
super.computeScroll();
//判斷scroller滾動是否完成
if (mScroller.computeScrollOffset()) {
// 這裡調用View的scrollTo()完成實際的滾動
scrollTo(0, mScroller.getCurrY());
//刷新試圖
postInvalidate();
}
}
最後一步,設置一些必要的監聽回調,和輔助方法
/**
* 監聽的接口定義
*/
public interface ScrollChangeListener{
void scrollY(int y);
void onScollStateChange(int type);
}
/**
* 設置監聽
* @param scrollChangeListener
*/
public void setScrollChangeListener(ScrollChangeListener scrollChangeListener) {
this.scrollChangeListener = scrollChangeListener;
}
/**
* 回到第一個頁面的頂部
*/
public void scrollToTop(){
mScroller.startScroll(0, getScrollY(), 0, -getScrollY());
scrollChangeListener.onScollStateChange(TOP);
postInvalidate();
}
android仿ios實現分段選擇控件UISegmentedControl
在ios7中有一種扁平風格的控件叫做分段選擇控件UISegmentedControl,控件上橫放或豎放著幾個被簡單線條隔開的按鈕,每次點擊能切換不同的按鈕和按鈕所對應的界
Android事件處理
一、Android事件現代的用戶界面,都是以事件來驅動的來實現人機交換的。而Android上的一套UI控件,無非就是派發鼠標和鍵盤事件,然後每個控件收到相應的事件之後,做
android 在有 簽名文件的情況下,找回 password 和 alias
哈哈,這種需求我也是醉了。今天有個搞ios的朋友(以前公司同事,現在是Leader)問我他們公司安卓要做版本升級,然後簽名文件有但是password 和 alias忘記
Android編程之SparseArray(E)詳解
最近編程時,發現一個針對HashMap的一個提示:翻譯過來就是:用SparseArray來代替會有更好性能。那我們就來看看源碼中SparseArray到底做了哪些事情:一