本文出自 “阿敏其人” 簡書博客,轉(zhuǎn)載或引用請注明出處恐似。
嗯,打開名片全能王掃描名片詳情傍念,我們發(fā)現(xiàn)iOS和Android的樣式是不一樣的矫夷,簡單來說,iOS的好看一些憋槐。大概如下圖
既然這樣双藕,就來簡單地仿造一個(gè)Android版吧。
先看仿造后的成品效果圖:
大概的功能是出來的阳仔,接下來忧陪,上代碼。
一近范、自定義拉伸控件
分析:把要自定義的控件分為兩個(gè)部分嘶摊,一個(gè)是頂部部分,簡稱為upPart评矩,一個(gè)是底部部分叶堆,簡稱為downPart。
upPart 就是上面的那張圖片斥杜,僅此而已蹂空。
downPart 就是下面的灰色覆蓋層和很多文字信息俯萌。
一、1上枕、準(zhǔn)備好的兩個(gè)布局文件
upPart 布局
<?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="160dp">
<ImageView
android:id="@+id/mIvTopPic"
android:layout_width="match_parent"
android:layout_height="160dp"
android:background="#66ff0000"
/>
</LinearLayout>
.
.
.
downPart 布局
<?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">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="160dp"
android:background="#66383636">
<TextView
android:id="@+id/mIvCircle"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/shape_circle_gray_white"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="蒙"
android:gravity="center"
android:textSize="20sp"
/>
<TextView
android:id="@+id/mIvDownGrayPic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_centerHorizontal="true"
android:layout_below="@id/mIvCircle"
android:text="蒙奇-D-路飛"
android:gravity="center"
android:textSize="20sp"
android:textColor="#ffffff"
/>
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="TEXT 1"
android:background="#660000FF"
android:layout_margin="10dp"
android:gravity="center"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="TEXT 2"
android:background="#660000FF"
android:layout_margin="10dp"
android:gravity="center"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="TEXT 3"
android:background="#660000FF"
android:layout_margin="10dp"
android:gravity="center"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="TEXT 4"
android:background="#660000FF"
android:layout_margin="10dp"
android:gravity="center"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="TEXT 5"
android:background="#660000FF"
android:layout_margin="10dp"
android:gravity="center"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="TEXT 6"
android:background="#660000FF"
android:layout_margin="10dp"
android:gravity="center"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="TEXT 1"
android:background="#660000FF"
android:layout_margin="10dp"
android:gravity="center"
/>
</LinearLayout>
.
.
.
一咐熙、2、自定義控件 StretchView
過程的大概這么走:
1辨萍、onMeasure測量一下
2棋恼、onLayout布局?jǐn)[放位置
3、利用onTouchEvent和viewDragHelper實(shí)現(xiàn)平滑滾動
主要邏輯就在StretchDraeHelper里面
- A:為了實(shí)現(xiàn)類似下拉回彈的效果锈玉,我們簡單地直接在clampViewPositionVertical里面返回top爪飘。不做嚴(yán)格的邊界控制。
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
//return super.clampViewPositionVertical(child, top, dy);
showTag("clampViewPositionVertical "+ top);
// 顏色的邊界控制
/*if(top<0){
return 0;
}else if(top>mUpPartHeight){
return mUpPartHeight;
}*/
return top;
}
- B:正常來說拉背,我們以為upPart的高度作為伸縮/展開的臨界判斷標(biāo)準(zhǔn)师崎,但是為了向上或者向下的速度足夠快的時(shí)候,也執(zhí)行伸縮或者展開椅棺,我們另外進(jìn)行了判斷:
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
// 方法的參數(shù)里面沒有top犁罩,那么我們就采用 getTop()這個(gè)方法
int releasePartTop = mDownPart.getTop();
showTag("記錄 yvel "+yvel);
float changeStatuValue = 200;
if(yvel>changeStatuValue && isFullStretch == false){ // 關(guān)閉狀態(tài)下,向下的滑動速率足夠即使不到一半也展開downPartView
openStretchView();
}else if(yvel<-changeStatuValue && isFullStretch == true){ // 打開狀態(tài),向上的滑動速率足夠也關(guān)閉downPartView
closeStretchView();
}else{ // 普通滑動速率,以為upPart的中間點(diǎn)為臨界點(diǎn)
if((mUpPartHeight*0.5)>releasePartTop){
//mDownPart.layout(0,0, mUpPartWidth, mUpPartHeight);
// 利用smoothSlideViewTo 產(chǎn)生平滑過渡的效果 (需要結(jié)合invalidate)
closeStretchView();
}else{
//mDownPart.layout(0, mUpPartHeight,mDownPartWidth, mUpPartHeight + mDownPartHeight);
openStretchView();
}
}
invalidate();
super.onViewReleased(releasedChild, xvel, yvel);
}
public class StretchView extends ViewGroup{
public static String TAG = "STRETCH";
private View mUpPart; // 上半部分
private View mDownPart; // 下半部分
private ViewDragHelper viewDragHelper;
private int mUpPartWidth;
private int mUpPartHeight;
private int mDownPartWidth;
private int mDownPartHeight;
private boolean isFullStretch = true;
public StretchView(Context context) {
super(context);
}
public StretchView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StretchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mUpPart = getChildAt(0);
mDownPart = getChildAt(1);
viewDragHelper = ViewDragHelper.create(this,new StretchDraeHelper());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// measure upPart
LayoutParams upPartLayoutParams = mUpPart.getLayoutParams();
int upPartMeasureHeight = MeasureSpec.makeMeasureSpec(upPartLayoutParams.height,MeasureSpec.EXACTLY);
mUpPart.measure(widthMeasureSpec,upPartMeasureHeight);
// measure downPart
LayoutParams downLayoutParams = mDownPart.getLayoutParams();
int downMeasurePartHeight = MeasureSpec.makeMeasureSpec(downLayoutParams.height,MeasureSpec.EXACTLY);
mDownPart.measure(widthMeasureSpec,downMeasurePartHeight);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mUpPartWidth = mUpPart.getMeasuredWidth();
mUpPartHeight = mUpPart.getMeasuredHeight();
showTag("mUpPartWidth "+ mUpPartWidth);
showTag("mUpPartHeight "+ mUpPartHeight);
mUpPart.layout(0,0, mUpPartWidth, mUpPartHeight); // 擺放上部分的位置
mDownPartWidth = mDownPart.getMeasuredWidth();
mDownPartHeight = mDownPart.getMeasuredHeight();
showTag("mDownPartWidth "+ mDownPartWidth);
showTag("mDownPartHeight "+ mDownPartHeight);
mDownPart.layout(0, mUpPartHeight,
mDownPartWidth, mUpPartHeight + mDownPartHeight); // 擺放刪除部分的位置
}
/**
* ViewDragHelper
*
* 使用ViewDragHelper必須復(fù)寫onTouchEvent并調(diào)用這個(gè)方法,才能使touch被消費(fèi)
*/
class StretchDraeHelper extends ViewDragHelper.Callback{
/**
* Touch的down事件會回調(diào)這個(gè)方法 tryCaptureView
*
* @Child:指定要?jiǎng)拥暮⒆? (哪個(gè)孩子需要?jiǎng)悠饋恚? * @pointerId: 點(diǎn)的標(biāo)記
* @return : ViewDragHelper是否繼續(xù)分析處理 child的相關(guān)touch事件
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return mDownPart == child;
}
/**
*
* 捕獲了水平方向移動的位移數(shù)據(jù)
* @param child 移動的孩子View
* @param left 父容器的左上角到孩子View的距離
* @param dx 增量值,其實(shí)就是移動的孩子View的左上角距離控件(父親)的距離两疚,包含正負(fù)
* @return 如何動
*
* 調(diào)用完此方法床估,在android2.3以上就會動起來了,2.3以及以下是海動不了的
* 2.3不兼容怎么辦诱渤?沒事丐巫,我們復(fù)寫onViewPositionChanged就是為了解決這個(gè)問題的
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return super.clampViewPositionHorizontal(child, left, dx);
}
/**
* 捕獲了垂直方向移動的位移數(shù)據(jù)
* @param child
* @param top
* @param dy
* @return
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
//return super.clampViewPositionVertical(child, top, dy);
showTag("clampViewPositionVertical "+ top);
// 顏色的邊界控制
/*if(top<0){
return 0;
}else if(top>mUpPartHeight){
return mUpPartHeight;
}*/
return top;
}
/**
* 當(dāng)View的位置改變時(shí)的回調(diào)
* @param changedView 哪個(gè)View的位置改變了
* @param left changedView的left
* @param top changedView的top
* @param dx x方向的上的增量值
* @param dy y方向上的增量值 取值范圍為 ±24000 之間 向下為正,向上負(fù)
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
invalidate();
showTag("onViewPositionChanged "+ top);
showTag("onViewPositionChanged "+ left);
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
// 方法的參數(shù)里面沒有top,那么我們就采用 getTop()這個(gè)方法
int releasePartTop = mDownPart.getTop();
showTag("記錄 yvel "+yvel);
float changeStatuValue = 200;
if(yvel>changeStatuValue && isFullStretch == false){ // 關(guān)閉狀態(tài)下,向下的滑動速率足夠即使不到一半也展開downPartView
openStretchView();
}else if(yvel<-changeStatuValue && isFullStretch == true){ // 打開狀態(tài),向上的滑動速率足夠也關(guān)閉downPartView
closeStretchView();
}else{ // 普通滑動速率,以為upPart的中間點(diǎn)為臨界點(diǎn)
if((mUpPartHeight*0.5)>releasePartTop){
//mDownPart.layout(0,0, mUpPartWidth, mUpPartHeight);
// 利用smoothSlideViewTo 產(chǎn)生平滑過渡的效果 (需要結(jié)合invalidate)
closeStretchView();
}else{
//mDownPart.layout(0, mUpPartHeight,mDownPartWidth, mUpPartHeight + mDownPartHeight);
openStretchView();
}
}
invalidate();
super.onViewReleased(releasedChild, xvel, yvel);
}
/**
* 整個(gè)View拓展起來
*/
private void openStretchView() {
viewDragHelper.smoothSlideViewTo(mDownPart,0,mUpPartHeight);
isFullStretch = true;
}
/**
* 整個(gè)view收縮起來
* @return
*/
private void closeStretchView() {
viewDragHelper.smoothSlideViewTo(mDownPart,0,0);
isFullStretch = false;
}
}
@Override
public void computeScroll() {
//super.computeScroll();
// 把捕獲的View適當(dāng)?shù)臅r(shí)間移動勺美,其實(shí)也可以理解為 smoothSlideViewTo 的模擬過程還沒完成
if(viewDragHelper.continueSettling(true)){
invalidate();
}
// 其實(shí)這個(gè)動畫過渡的過程大概在怎么走呢递胧?
// 1、smoothSlideViewTo方法進(jìn)行模擬數(shù)據(jù)赡茸,模擬后就就調(diào)用invalidate();
// 2缎脾、invalidate()最終調(diào)用computeScroll,computeScroll做一次細(xì)微動畫坛掠,
// computeScroll判斷模擬數(shù)據(jù)是否徹底完成,還沒完成會再次調(diào)用invalidate
// 3治筒、遞歸調(diào)用屉栓,知道數(shù)據(jù)noni完成。
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//return super.onTouchEvent(event);
/**Process a touch event received by the parent view. This method will dispatch callback events
as needed before returning. The parent view's onTouchEvent implementation should call this. */
viewDragHelper.processTouchEvent(event); // 使用ViewDragHelper必須復(fù)寫onTouchEvent并調(diào)用這個(gè)方法
return true; //消費(fèi)這個(gè)touch
}
private void showTag(String str){
Log.d(TAG,str);
}
}
二耸袜、使用 StretchView 控件
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<android.support.v7.widget.Toolbar
android:id="@+id/mToolBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#00000000"
android:layout_marginTop="20dp"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
/>
<com.amqr.likepaperstretch.widget.StretchView
android:id="@+id/mStretchView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/item_stretch_up"/>
<include layout="@layout/item_stretch_down"/>
</com.amqr.likepaperstretch.widget.StretchView>
</RelativeLayout>
需要注意的是友多,com.amqr.likepaperstretch.widget.StretchView里面的include先后順序非常重要,不可隨意顛倒堤框。
.
.
.
ShowActivity
public class ShowActivity extends AppCompatActivity{
private Toolbar mToolBar;
private StretchView mStretchView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show);
mToolBar = (Toolbar) findViewById(R.id.mToolBar);
mToolBar.setTitle("Title");
mToolBar.setTitleTextColor(getResources().getColor(R.color.white));
setSupportActionBar(mToolBar);
mToolBar.setOverflowIcon(getResources().getDrawable(R.drawable.icon_menu)); // 指定菜單按鈕圖標(biāo)
mToolBar.setNavigationIcon(R.drawable.arrow_left); // 返回箭頭
mToolBar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
mToolBar.setOnMenuItemClickListener(onMenuItemClick);
mStretchView = (StretchView)findViewById(R.id.mStretchView);
mStretchView.findViewById(R.id.mIvTopPic).setBackgroundResource(R.drawable.pic_default);
//Toast.makeText(ShowActivity.this,"彈出菜單",Toast.LENGTH_SHORT).show();
//只對api19以上版本有效
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setTranslucentStatus(true);
}
}
@TargetApi(19)
private void setTranslucentStatus(boolean on) {
Window win = getWindow();
WindowManager.LayoutParams winParams = win.getAttributes();
final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
if (on) {
winParams.flags |= bits;
} else {
winParams.flags &= ~bits;
}
win.setAttributes(winParams);
}
// 創(chuàng)建關(guān)聯(lián)菜單
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main,menu);
return true;
}
// 菜單的點(diǎn)擊回調(diào)
private Toolbar.OnMenuItemClickListener onMenuItemClick = new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
String msg = "";
switch (menuItem.getItemId()) {
case R.id.action_ball:
msg += "Click ball";
break;
case R.id.action_tip:
msg += "Click action_tip";
break;
case R.id.action_menu:
msg += "Click setting";
break;
}
if(!msg.equals("")) {
Toast.makeText(ShowActivity.this, msg, Toast.LENGTH_SHORT).show();
}
return true;
}
};
}
為了讓Toolbar的菜單欄總是可以點(diǎn)擊域滥,所以我們單獨(dú)放在自定義控件的上方纵柿,而不是在做自定義控件的時(shí)候就弄在一起。
我們通過這兩段代碼去掉標(biāo)題欄启绰,起到沉浸的效果昂儒。
//只對api19以上版本有效
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setTranslucentStatus(true);
}
@TargetApi(19)
private void setTranslucentStatus(boolean on) {
Window win = getWindow();
WindowManager.LayoutParams winParams = win.getAttributes();
final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
if (on) {
winParams.flags |= bits;
} else {
winParams.flags &= ~bits;
}
win.setAttributes(winParams);
}
大概就是這樣,其實(shí)還有很多可以改進(jìn)的空間的委可,比如弄一個(gè)伸縮或者展開實(shí)現(xiàn)其他需求的接口渊跋,比如提供打開或者關(guān)閉的方法,等等着倾。如果需要拾酝,可以自行調(diào)整。
本篇完卡者。