Android自定義控件:打造自己的QQ空間主頁

前面已經(jīng)實(shí)現(xiàn)過仿QQ的List抽屜效果以及仿QQ未讀消息拖拽效果皇筛,具體請見:
Android自定義控件:類QQ抽屜效果
Android自定義控件:類QQ未讀消息拖拽效果
趁熱打鐵答憔,這次我們實(shí)現(xiàn)QQ空間的主頁全效果蝶锋,先貼上我們最終的完成效果圖:

這里寫圖片描述

可以看到易稠,我們實(shí)現(xiàn)了如下效果:

  1. 下拉拖拽視差效果
  2. 透明狀態(tài)欄+TitleBar
  3. 狀態(tài)欄+TitleBar顏色動(dòng)態(tài)漸變
  4. 下拉加載更多
  5. 點(diǎn)擊按鈕∨彈出PopupWindow list選項(xiàng)+模糊背景效果
  6. 點(diǎn)擊按鈕+頂部彈出PopupWindow界面+模糊背景效果

下拉拖拽視差效果

第一步先實(shí)現(xiàn)拖拽視差效果脐帝,也就是下拉的時(shí)候,有一種阻滯感仆百,然后手抬起的時(shí)候厕隧,會(huì)稍微回彈一下。
在實(shí)現(xiàn)效果之前俄周,我們先看一下實(shí)現(xiàn)原理吁讨,我們看一下下面這張圖:


這里寫圖片描述

實(shí)際上呢,一整個(gè)視差效果界面峦朗,其實(shí)就是一個(gè)ListView建丧。我們給listView設(shè)置了一個(gè)headView,然后設(shè)置headView 布局的scaleType為centerCrop甚垦,取src圖片的中部也就是圖中綠色部分茶鹃,這部分是初始顯示區(qū)域。headView的src圖片上下部分實(shí)際上是處于界面之外沒有顯示出來艰亮,也就是圖中的棕色部分闭翩。
下面貼上頭布局代碼:

list_item_head.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="330dp"
        android:scaleType="centerCrop"
        android:id="@+id/iv_head"
        android:src="@mipmap/parallax_img"/>

</LinearLayout>

然后在Activity中為listView添加頭布局:

    private void init() {
        for (int i = 0; i < 30; i++) {
            list.add("user - " + i);
        }
        lvParallax = (ListView) findViewById(R.id.lv_parallax);
        lvParallax.setOverScrollMode(ListView.OVER_SCROLL_NEVER);//滑到底部頂部不顯示藍(lán)色陰影
        View headerView = View.inflate(this, R.layout.list_item_head, null);//添加header
        ImageView ivHead = (ImageView) headerView.findViewById(R.id.iv_head);
        lvParallax.initParallaxImageParams(ivHead);
        lvParallax.addHeaderView(headerView);
        lvParallax.setAdapter(new ParallaxAdapter(this, list));
    }

item布局代碼就不貼了,我們看看現(xiàn)在運(yùn)行的效果:

這里寫圖片描述

注:為了方便截圖迄埃,后面的圖都是運(yùn)行在模擬器(480x800)上的效果截圖疗韵,所以顯示效果肯定跟最開始的真機(jī)(720x1280)效果有一定的區(qū)別,不過此處只是做演示侄非,這點(diǎn)小事就先忽略啦~ =蕉汪。=**

既然布局已經(jīng)完成了,那么我們接下來實(shí)現(xiàn)視差拖拽效果逞怨。
既然要拖拽者疤,我們肯定要自定義一個(gè)ListView并且重寫其onTouchEvent以及overScrollBy方法。

首先我們要思考的是叠赦,我們?nèi)绾卧谧远x控件中拿到我們headView的高度以及圖片的高度呢驹马?由于我們的headView參數(shù)是在Activity的onCreate中初始化的,但是在onCreate中無法通過getHeight()和getWidth()拿到headView的高度和寬度除秀,因?yàn)閂iew組件布局要在onResume回調(diào)后完成糯累。那么我們?nèi)绾卧趏nCreate中拿到headView的高度參數(shù)呢?這里我們通過getViewTreeObserver().addOnGlobalLayoutListener()來獲得寬度或者高度册踩。這是獲得一個(gè)view的寬度和高度的方法之一泳姐。
OnGlobalLayoutListener 是ViewTreeObserver的內(nèi)部類,當(dāng)一個(gè)視圖樹的布局發(fā)生改變時(shí)暂吉,可以被ViewTreeObserver監(jiān)聽到胖秒,這是一個(gè)注冊監(jiān)聽視圖樹的觀察者(observer),在視圖樹的全局事件改變時(shí)得到通知慕的。ViewTreeObserver不能直接實(shí)例化扒怖,而是通過getViewTreeObserver()獲得。
不多說业稼,上代碼:

    /**
     * 初始化ParallaxImage的初始參數(shù)
     *
     * @param imageView
     */
    public void initParallaxImageParams(final ImageView imageView) {
        this.ivHead = imageView;
        //設(shè)定ImageView最大高度
        imageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver
                .OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                imageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                orignalHeight = imageView.getHeight();
                //Log.e("tag", "orignalHeight = " + orignalHeight);
                //獲取圖片的高度
                int drawbleHeight = imageView.getDrawable().getIntrinsicHeight();
                maxHeight = orignalHeight > drawbleHeight ? orignalHeight * 2 : drawbleHeight;
                //Log.e("tag", "maxHeight = " + maxHeight);
            }
        });
    }

在onGlobalLayout中增加一層判斷盗痒,當(dāng)headView初始高度大于圖片高度時(shí),我們?nèi)〉纳舷禄瑒?dòng)最大高度是headView*2低散。因?yàn)閺母旧蟻碇v俯邓,我們肯定是要保證headView上下部分肯定是超出界面之外的,所以這里的maxHeight肯定是要大于headView的高度的熔号。

然后重寫overScrollBy方法稽鞭,overScrollBy會(huì)在listview滑動(dòng)到頭的時(shí)候執(zhí)行,可以獲取到繼續(xù)滑動(dòng)的距離和方向引镊。當(dāng)滑動(dòng)到頭的時(shí)候朦蕴,我們通過繼續(xù)滾動(dòng)的距離篮条,動(dòng)態(tài)設(shè)置headView的高度,這樣達(dá)到一個(gè)拖動(dòng)顯示的效果吩抓。

    /**
     * 在listview滑動(dòng)到頭的時(shí)候執(zhí)行涉茧,可以獲取到繼續(xù)滑動(dòng)的距離和方向
     * deltaX:繼續(xù)滑動(dòng)x方向的距離
     * deltaY:繼續(xù)滑動(dòng)y方向的距離     負(fù):表示頂部到頭   正:表示底部到頭
     * maxOverScrollX:x方向最大可以滾動(dòng)的距離
     * maxOverScrollY:y方向最大可以滾動(dòng)的距離
     * isTouchEvent: true: 是手指拖動(dòng)滑動(dòng)     false:表示fling靠慣性滑動(dòng);
     */
    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int
            scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean
                                           isTouchEvent) {
        //Log.e("tag", "deltaY: " + deltaY + "  isTouchEvent:" + isTouchEvent);
        if (deltaY < 0 && isTouchEvent) {//頂部到頭,并且是手動(dòng)拖到頂部
            if (ivHead != null) {
                int newHeight = ivHead.getHeight() - deltaY / 3;
                if (newHeight > maxHeight) {
                    newHeight = maxHeight;//限定拖動(dòng)最大高度范圍
                }
                ivHead.getLayoutParams().height = newHeight;//重新設(shè)置ivHead的高度值
                //使布局參數(shù)生效
                ivHead.requestLayout();
            }
        }
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
                maxOverScrollX, maxOverScrollY, isTouchEvent);
    }

最后重寫onTouchEvent方法疹娶,在這里檢測手抬起動(dòng)作伴栓,在手抬起的時(shí)候通過一個(gè)屬性動(dòng)畫回復(fù)headView原本的高度:

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (MotionEventCompat.getActionMasked(ev) == MotionEvent.ACTION_UP) {
            //放手的時(shí)候講imageHead的高度緩慢從當(dāng)前高度恢復(fù)到最初高度
            final ValueAnimator animator = ValueAnimator.ofInt(ivHead.getHeight(), orignalHeight);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    int animateValue = (int) animator.getAnimatedValue();
                    ivHead.getLayoutParams().height = animateValue;
                    //使布局參數(shù)生效
                    ivHead.requestLayout();
                }
            });
            animator.setInterpolator(new OvershootInterpolator(3.f));//彈性插值器
            animator.setDuration(350);
            animator.start();
        }
        return super.onTouchEvent(ev);
    }

最后的視差拖拽效果實(shí)現(xiàn)如下:


這里寫圖片描述

透明狀態(tài)欄+TitleBar

視差拖拽效果實(shí)現(xiàn)完成,當(dāng)然離我們最終要的漂漂的效果還有距離雨饺,距離在哪呢钳垮,首先我們沒有TitleBar,再接著呢额港,這個(gè)狀態(tài)欄饺窿,也太丑了!R普丁短荐!
下面首先實(shí)現(xiàn)透明狀態(tài)欄
在Activity setContentView(R.layout.activity_main)之后,我們執(zhí)行下面的代碼叹哭,要注意的是setStatusBarColor這個(gè)方法忍宋,也就是設(shè)置狀態(tài)欄顏色的方法,是API21也就是5.0以后才有的方法风罩,在5.0之前是無法實(shí)現(xiàn)的糠排,不過現(xiàn)在7.0都出來了,5.0之前的機(jī)型應(yīng)該也不多了超升。

     /**
     * 初始化狀態(tài)欄狀態(tài)
     * 設(shè)置Activity狀態(tài)欄透明效果
     * 隱藏ActionBar
     */
    private void initState() {
        //將狀態(tài)欄設(shè)置成透明色
        UIUtils.setBarColor(this, Color.TRANSPARENT);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.hide();
        }
    }
    
    /**
     * 設(shè)置狀態(tài)欄背景色
     * 4.4以下不處理
     * 4.4使用默認(rèn)沉浸式狀態(tài)欄
     * @param color
     */
    public static void setBarColor(Activity activity, int color) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Window win = activity.getWindow();
            View decorView = win.getDecorView();
            win.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//沉浸式狀態(tài)欄
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//android5.0及以上才有透明效果
                win.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//清除flag
                //讓應(yīng)用的主體內(nèi)容占用系統(tǒng)狀態(tài)欄的空間
                int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
                decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | option);
                win.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                win.setStatusBarColor(color);//設(shè)置狀態(tài)欄背景色
            }
        }
    }

辣么我們現(xiàn)在的效果如何呢入宦?


這里寫圖片描述

嚯,已經(jīng)成功把狀態(tài)欄變透明了室琢。
接下來看看TitleBar乾闰,由于我們實(shí)際上是將整個(gè)應(yīng)用沾滿整個(gè)屏幕,也就是說App應(yīng)用主體實(shí)際上占用了狀態(tài)欄的空間并且狀態(tài)欄背景設(shè)置成了透明盈滴,所以實(shí)現(xiàn)了現(xiàn)在這種應(yīng)用作為狀態(tài)欄背景的效果涯肩。在應(yīng)用沒有占據(jù)全屏的情況下,布局應(yīng)該是從狀態(tài)欄之下開始布局的巢钓,但是現(xiàn)在應(yīng)用實(shí)際上是從屏幕(0,0)開始布局的病苗,所以在實(shí)際應(yīng)用中,TitleBar的高度應(yīng)該是設(shè)置為狀態(tài)欄高度+原本期望TitleBar的高度症汹。
下面貼上TitleBar代碼

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tb_title"
    android:layout_width="match_parent"
    android:layout_height="90dp">

    <Button
        android:id="@+id/btn_back"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:background="@mipmap/back"/>

    <RelativeLayout
        android:id="@+id/rl_title"
        android:layout_width="0dp"
        android:layout_height="80dp"
        android:layout_marginTop="43dp"
        android:layout_weight="1">

        <ImageView
            android:id="@+id/iv_title"
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_toLeftOf="@+id/tv_title"
            android:layout_marginRight="5dp"
            android:layout_marginTop="2dp"
            android:src="@mipmap/refesh"
            android:visibility="invisible"/>

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="80dp"
            android:background="@android:color/transparent"
            android:text="Title"
            android:layout_centerInParent="true"
            android:textColor="@android:color/white"
            android:textSize="20sp"/>
    </RelativeLayout>


    <Button
        android:id="@+id/btn_add"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_gravity="center"
        android:layout_marginRight="20dp"
        android:layout_marginTop="10dp"
        android:background="@mipmap/add_white"/>
</LinearLayout>

最后將TitleBar和ListView放在一個(gè)FrameLayout中硫朦,界面上的布局,基本完成背镇。


這里寫圖片描述

狀態(tài)欄+TitleBar顏色動(dòng)態(tài)漸變

基本界面已經(jīng)實(shí)現(xiàn)完成咬展,接下來我們看看怎么實(shí)現(xiàn)狀態(tài)欄和TitleBar顏色漸變泽裳。前面我們說了,TitleBar和ListView是放在一個(gè)FrameLayout中的破婆。所以思路應(yīng)該很明確了涮总,就是在這個(gè)FrameLayout中動(dòng)態(tài)的設(shè)置TitleBar的背景色,由于狀態(tài)欄實(shí)際是透明背景然后被TitleBar充滿的荠割,所以實(shí)際上我們這里說的狀態(tài)欄+TitleBar顏色動(dòng)態(tài)漸變其實(shí)單修改TitleBar的背景色就可以了。

首先我們實(shí)現(xiàn)一個(gè)自定義GradientLayout 旺矾,在GradientLayout中 給ParallaxListView設(shè)置一個(gè)OnScrollListener 蔑鹦,將根據(jù)ParallaxListView滑動(dòng)的距離和預(yù)設(shè)值求出一個(gè)fraction值,然后根據(jù)fraction和估值器計(jì)算出顏色值并且設(shè)置給TitleBar達(dá)到動(dòng)態(tài)更新TitleBar和狀態(tài)欄顏色的效果箕宙。

由于TitlBar右上角的添加按鈕需要根據(jù)滑動(dòng)距離更新背景嚎朽,所以這里我們增加一個(gè)接口OnGradientStateChangeListenr ,TitleBar實(shí)現(xiàn)這個(gè)接口柬帕,然后根據(jù)GradientLayout傳過去的fraction值以及關(guān)鍵值來更新按鈕"+"的狀態(tài):

public class GradientLayout extends FrameLayout implements OnScrollListener {
    private TitleBar tb_title;
    private ParallaxListView plv;
    private static final float CRITICAL_VALUE = 0.5f;
    private OnGradientStateChangeListenr onGradientStateChangeListenr;
    private Context context;

    /**
     * 設(shè)置Gradient狀態(tài)監(jiān)聽
     * @param onGradientStateChangeListenr
     */
    public void setOnGradientStateChangeListenr(OnGradientStateChangeListenr onGradientStateChangeListenr){
        this.onGradientStateChangeListenr = onGradientStateChangeListenr;
    }

    public GradientLayout(Context context) {
        this(context, null);
    }

    public GradientLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GradientLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() != 2) {
            throw new IllegalArgumentException("only can 2 child in this view");
        } else {
            if (getChildAt(0) instanceof ParallaxListView) {
                plv = (ParallaxListView) getChildAt(0);
                plv.setOnScrollListener(this);
            } else {
                throw new IllegalArgumentException("child(0) must be ParallaxListView");
            }
            tb_title = (TitleBar) getChildAt(1);
            tb_title.setTitleBarListenr(this);
        }
    }

    /**
     * 設(shè)置title背景色
     *
     * @param color
     */
    public void setTitleBackground(int color) {
        tb_title.setBackgroundColor(color);
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int
            totalItemCount) {
        if (firstVisibleItem == 0) {
            View headView = view.getChildAt(0);
            if (headView != null) {
                //如果上滑超過headView高度值一半+title高度哟忍,開啟伴隨動(dòng)畫
                float slideValue = Math.abs(headView.getTop()) - headView.getHeight() / 2.f +
                        tb_title.getHeight();
                if (slideValue < 0)
                    slideValue = 0;
                float fraction = slideValue / (headView.getHeight() / 2.f);
                if (fraction > 1) {
                    fraction = 1;
                }
                //Log.e("tag", "fraction = " + fraction);
                excuteAnim(fraction);
            }
        } else {
            float fraction = 1;
            excuteAnim(fraction);
        }
    }

    private void excuteAnim(float fraction) {
        int color = (int) ColorUtil.evaluateColor(fraction, Color.parseColor("#0000ccff"), Color
                .parseColor("#ff00ccff"));
        setTitleBackground(color);
        onGradientStateChangeListenr.onChange(fraction, CRITICAL_VALUE);
    }

    /**
     * 設(shè)置TitleBar text
     * @param msg
     */
    public void setTitleText(String msg){
        tb_title.setTitleText(msg);
    }

    /**
     * Gradient變化臨界值監(jiān)聽
     */
    public interface OnGradientStateChangeListenr{
        /**
         * 當(dāng)fraction超過臨界值時(shí)回調(diào)
         * @param fraction
         * @param criticalValue
         */
        public void onChange(float fraction, float criticalValue);
    }
}

TitleBar實(shí)現(xiàn)OnGradientStateChangeListenr

/**
     * 設(shè)置Gradient臨界值監(jiān)聽
     *
     * @param gl
     */
    public void setTitleBarListenr(GradientLayout gl) {
        gl.setOnGradientStateChangeListenr(new OnGradientStateChangeListenr() {
            @Override
            public void onChange(float fraction, float criticalValue) {
                /**
                 * 當(dāng)變化值超過臨界值
                 */
                if (fraction >= criticalValue) {
                    btn_add.setBackgroundResource(R.mipmap.add_trans);
                } else {
                    btn_add.setBackgroundResource(R.mipmap.add_white);
                }
            }
        })
    }

至此我們的效果如下:


這里寫圖片描述

下拉加載更多

感覺現(xiàn)在基本已經(jīng)像一個(gè)比較靠譜的demo了,現(xiàn)在繼續(xù)增加下拉加載更多的功能陷寝。其實(shí)有了前面的鋪墊锅很,下拉加載實(shí)現(xiàn)起來其實(shí)非常簡單。
首先在ParallaxListView監(jiān)聽下拉拖拽的距離凤跑,然后在松手的時(shí)候根據(jù)拖拽距離計(jì)算出是否出發(fā)加載更多爆安,最后通過接口回調(diào)的方式將這個(gè)下拉刷新的狀態(tài)以及結(jié)果通知給GradientLayout,GradientLayout又通過接口回調(diào)的方式通知TitleBar更新界面仔引。不多說扔仓,直接上代碼,要注意的一點(diǎn)是咖耘,為了獨(dú)立開ParallaxListView和TitleBar翘簇,ParallaxListView和TitleBar的狀態(tài)更新全部通過父Layout GradientLayout。

ParallaxListView增加刷新接口以及模擬請求數(shù)據(jù)

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (MotionEventCompat.getActionMasked(ev) == MotionEvent.ACTION_UP) {
            //如果松手時(shí)headView滑動(dòng)的距離大于預(yù)設(shè)值儿倒,回調(diào)onRefesh
            //Log.e("tag", "ivHead.getHeight() = " + ivHead.getHeight());
            //Log.e("tag", "orignalHeight = " + orignalHeight);
            if (ivHead.getHeight() - orignalHeight > 60) {
                if(onRefeshChangeListener != null){
                    onRefeshChangeListener.onListRefesh();
                    if(!isRefeshing){//當(dāng)前不是刷新狀態(tài)時(shí)
                        getData();
                        isRefeshing = true;
                    }
                }
            }
            //放手的時(shí)候講imageHead的高度緩慢從當(dāng)前高度恢復(fù)到最初高度
            final ValueAnimator animator = ValueAnimator.ofInt(ivHead.getHeight(), orignalHeight);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    int animateValue = (int) animator.getAnimatedValue();
                    ivHead.getLayoutParams().height = animateValue;
                    //使布局參數(shù)生效
                    ivHead.requestLayout();
                }
            });
            animator.setInterpolator(new OvershootInterpolator(3.f));//彈性插值器
            animator.setDuration(350);
            animator.start();
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 開啟一個(gè)線程模擬網(wǎng)絡(luò)請求操作
     */
    private void getData(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //test
                onRefeshChangeListener.onListRefeshFinish(true);
                isRefeshing = false;
                //onRefeshChangeListener.onRefeshFinish(false);
            }
        }).start();
    }

GradientLayout實(shí)現(xiàn)ParallaxListView.OnRefeshChangeListener并且新增一個(gè)OnRefeshChangeListener接口用于將狀態(tài)給TitleBar版保,實(shí)際上GradientLayout相當(dāng)于ParallaxListView和TitleBar的傳話者。

    @Override
    public void onListRefesh() {
        onRefeshChangeListener.onListRefesh();
    }

    @Override
    public void onListRefeshFinish(final boolean isRefeshSuccess) {
        UIUtils.runOnUIThread(new Runnable() {
            @Override
            public void run() {
                if(isRefeshSuccess){
                    //Toast.makeText(UIUtils.getContext(), "refesh success.", Toast.LENGTH_SHORT).show();
                }else{
                    Toast.makeText(UIUtils.getContext(), "refesh failed.", Toast.LENGTH_SHORT).show();
                }
            }
        });
        //不論刷新成功還是失敗夫否,都要通知titleBar刷新完成
        onRefeshChangeListener.onListRefeshFinish();
    }

    /**
     * GradientLayout中的子list列表刷新狀態(tài)監(jiān)聽
     */
    public interface OnRefeshChangeListener{
        /**
         * 開始刷新列表找筝,請求數(shù)據(jù)
         */
        void onListRefesh();
        /**
         * 刷新列表完成
         */
        void onListRefeshFinish();
    }

TitleBar實(shí)現(xiàn)父Layout的接口,然后通過一個(gè)Tween動(dòng)畫實(shí)現(xiàn)刷新進(jìn)度圈圈的旋轉(zhuǎn):

    /**
     * 設(shè)置TitleBar監(jiān)聽
     *
     * @param gl
     */
    public void setTitleBarListenr(GradientLayout gl) {
        gl.setOnGradientStateChangeListenr(new OnGradientStateChangeListenr() {
            @Override
            public void onChange(float fraction, float criticalValue) {
                /**
                 * 當(dāng)變化值超過臨界值
                 */
                if (fraction >= criticalValue) {
                    btn_add.setBackgroundResource(R.mipmap.add_trans);
                } else {
                    btn_add.setBackgroundResource(R.mipmap.add_white);
                }
            }
        });
        gl.setOnRefeshChangeListener(new GradientLayout.OnRefeshChangeListener() {
            @Override
            public void onListRefesh() {
                UIUtils.runOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        iv_title.setVisibility(View.VISIBLE);
                        //執(zhí)行動(dòng)畫
                        Animation anim = AnimationUtils.loadAnimation(context, R.anim.refesh_roate);
                        anim.setInterpolator(new LinearInterpolator());
                        iv_title.startAnimation(anim);
                    }
                });
            }

            @Override
            public void onListRefeshFinish() {
                UIUtils.runOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        iv_title.setVisibility(View.INVISIBLE);
                        iv_title.clearAnimation();
                    }
                });
            }
        });
    }

現(xiàn)在再看看我們的效果慷吊,淚流滿面袖裕,終于實(shí)現(xiàn)大部分效果了!


這里寫圖片描述

點(diǎn)擊按鈕∨彈出PopupWindow list選項(xiàng)+模糊背景效果

接下來要實(shí)現(xiàn)的是QQ空間好友動(dòng)態(tài)列表選項(xiàng)彈出的效果溉瓶,QQ是彈出一個(gè)屏幕等寬的列表急鳄。我們這里實(shí)現(xiàn)的稍微跟QQ的有點(diǎn)不一樣谤民,我們這里實(shí)現(xiàn)的效果更像是3D touch的效果。

先來擼一擼思路疾宏,既然是彈出來张足,首相第一個(gè)想到的實(shí)現(xiàn)方法,當(dāng)然是PopupWindow坎藐,然后背景虛化为牍,其實(shí)網(wǎng)上也有很多的模糊虛化方法,然后再接著就是將我們要添加的View設(shè)到屏幕上岩馍。OK碉咆,思路很清晰簡單,然鵝蛀恩,真的辣么簡單嗎疫铜?

并沒有啊K弧?枪尽!一開始就出點(diǎn)了小意外顽馋,就是關(guān)于WindowManager.LayoutParams谓厘,由于這玩意的flag值實(shí)在是太多了,網(wǎng)上這類功能相關(guān)的資料又比較少寸谜,最后好一番折騰庞呕,總算是實(shí)現(xiàn)了我們要的效果,也就是虛化背景不滿全屏程帕,但是不知道為什么住练,模擬器狀態(tài)欄依然顯示的是半透明狀態(tài)欄,好在真機(jī)上運(yùn)行都一切正常愁拭,然后就妥妥的無視模擬器這個(gè)問題了讲逛。

先看看我們配置的WindowManager.LayoutParams,這里只列出來我們用到的幾個(gè)flag值岭埠,折騰了小半天盏混,最后也就用到這么幾個(gè),委屈的不行惜论,哈哈许赃。

    private void initParams() {
        params = new WindowManager.LayoutParams();
        params.width = MATCH_PARENT;
        params.height = MATCH_PARENT;
        params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN //布滿屏幕,忽略狀態(tài)欄
                | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS //透明狀態(tài)欄
                | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; //透明虛擬按鍵欄
        params.format = PixelFormat.TRANSLUCENT;//支持透明
        params.gravity = Gravity.LEFT | Gravity.TOP;
    }

接下來要思考的是將listView塞進(jìn)一個(gè)空layout馆类,這個(gè)地方要注意的是混聊,由于我們這里彈出的listView背景是一個(gè).9圖片峰搪,所以一定要記住將這個(gè).9圖片設(shè)置個(gè)listView做背景<致!!而不是設(shè)置給我們的空layout7钋骸J邸笋庄!

由于listView寬度我們希望是自適應(yīng)而不是充滿屏幕彤灶,所以我們要自定義一個(gè)listView,并且根據(jù)item的最大寬度設(shè)置listView的寬度展懈,下面貼上自定義listView的代碼销睁。

public class PopupListView extends ListView {
    private Context context;

    public PopupListView(Context context) {
        this(context, null);
    }

    public PopupListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PopupListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int maxWidth = measureWidthByChilds() + getPaddingLeft() + getPaddingRight();
        super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.UNSPECIFIED),
                heightMeasureSpec);//注意,這個(gè)地方一定是MeasureSpec.UNSPECIFIED
    }

    public int measureWidthByChilds() {
        int maxWidth = 0;
        View view = null;
        for (int i = 0; i < getAdapter().getCount(); i++) {
            view = getAdapter().getView(i, view, this);
            view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
            if (view.getMeasuredWidth() > maxWidth) {
                maxWidth = view.getMeasuredWidth();
            }
            view = null;
        }
        return maxWidth;
    }
}

有一點(diǎn)比較重要存崖,我們在popupWindow彈出來的時(shí)候冻记,需要攔截返回鍵事件,點(diǎn)擊返回鍵時(shí)dismiss掉popupWindow金句,如何攔截返回鍵事件呢檩赢?我們這里通過一個(gè)自定義layout吕嘀,重寫這個(gè)layout的dispatchKeyEvent事件然后暴露一個(gè)接口违寞,實(shí)際上相當(dāng)于對dispatchKeyEvent事件做了一次傳遞,然后在popupWindow中實(shí)現(xiàn)setDispatchKeyEventListener的回調(diào)偶房。

/**
 * 攔截WindowManager中view的按鍵事件趁曼,此處主要用于返回鍵事件攔截
 * Created by Horrarndoo on 2017/3/28.
 */
public class PopupRootLayout extends FrameLayout{
    private DispatchKeyEventListener mDispatchKeyEventListener;

    public PopupRootLayout(Context context) {
        super(context);
    }

    public PopupRootLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public PopupRootLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mDispatchKeyEventListener != null) {
            return mDispatchKeyEventListener.dispatchKeyEvent(event);
        }
        return super.dispatchKeyEvent(event);
    }

    public DispatchKeyEventListener getDispatchKeyEventListener() {
        return mDispatchKeyEventListener;
    }

    public void setDispatchKeyEventListener(DispatchKeyEventListener mDispatchKeyEventListener) {
        this.mDispatchKeyEventListener = mDispatchKeyEventListener;
    }

    //監(jiān)聽接口
    public static interface DispatchKeyEventListener {
        boolean dispatchKeyEvent(KeyEvent event);
    }
}

最后貼上PopupWindow的代碼,設(shè)置虛化背景和彈出/隱藏ListView都是通過屬性動(dòng)畫棕洋,比較簡單挡闰,代碼注釋也比較全,就不多做解釋了掰盘。

public class BlurPopupWindow {
    /**
     * 頂部彈出popupWindow關(guān)鍵字
     */
    public static final int KEYWORD_LOCATION_TOP = 1;
    /**
     * 點(diǎn)擊處彈出popupWindow關(guān)鍵字
     */
    public static final int KEYWORD_LOCATION_CLICK = 2;
    private Activity activity;
    private WindowManager.LayoutParams params;
    private boolean isDisplay;
    private WindowManager windowManager;
    private PopupRootLayout rootView;
    private ViewGroup contentLayout;

    private final int animDuration = 250;//動(dòng)畫執(zhí)行時(shí)間
    private boolean isAniming;//動(dòng)畫是否在執(zhí)行

    /**
     * BlurPopupWindow構(gòu)造函數(shù)
     *
     * @param activity 當(dāng)前彈出/消失BlurPopupWindow的Activity
     * @param view     要彈出/消失的view內(nèi)容
     *                 默認(rèn)從點(diǎn)擊處彈出/消失popupWindow
     */
    public BlurPopupWindow(Activity activity, View view) {
        initBlurPopupWindow(activity, view, KEYWORD_LOCATION_CLICK);
    }

    /**
     * BlurPopupWindow構(gòu)造函數(shù)
     *
     * @param activity 當(dāng)前彈出/消失BlurPopupWindow的Activity
     * @param view     要彈出/消失的view內(nèi)容
     * @param keyword  彈出/消失位置關(guān)鍵字 KEYWORD_LOCATION_TOP:頂部彈出
     *                 KEYWORD_LOCATION_CLICK:點(diǎn)擊位置彈出
     */
    public BlurPopupWindow(Activity activity, View view, int keyword) {
        initBlurPopupWindow(activity, view, keyword);
    }

    /**
     * BlurPopupWindow初始化
     *
     * @param activity 當(dāng)前彈出BlurPopupWindow的Activity
     * @param view     要彈出/消失的view內(nèi)容
     * @param keyword  彈出/消失位置關(guān)鍵字 KEYWORD_LOCATION_TOP:頂部彈出
     *                 KEYWORD_LOCATION_CLICK:點(diǎn)擊位置彈出
     */
    private void initBlurPopupWindow(Activity activity, View view, int keyword) {
        this.activity = activity;

        windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
        switch (keyword) {
            case KEYWORD_LOCATION_CLICK:
                view.setPadding(5, 10, 5, 0);//由于.9圖片有部分是透明摄悯,往下padding 10個(gè)pix,左右padding 5個(gè)pix為了美觀
                view.setBackgroundResource(R.drawable.popup_bg);
                break;
            case KEYWORD_LOCATION_TOP:
                ImageView imageView = (ImageView) view;
                imageView.setScaleType(ImageView.ScaleType.FIT_START);
                imageView.setImageDrawable(activity.getResources().getDrawable(R.mipmap.popup_top_bg));
                break;
            default:
                break;
        }
        initLayout(view, keyword);
    }

    private void initLayout(View view, final int keyword) {
        rootView = (PopupRootLayout) View.inflate(activity, R.layout.popupwindow_layout, null);
        contentLayout = (ViewGroup) rootView.findViewById(R.id.content_layout);

        initParams();

        contentLayout.addView(view);

        //點(diǎn)擊根布局時(shí), 隱藏彈出的popupWindow
        rootView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismissPopupWindow(keyword);
            }
        });

        rootView.setDispatchKeyEventListener(new DispatchKeyEventListener() {
            @Override
            public boolean dispatchKeyEvent(KeyEvent event) {
                if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                    if (rootView.getParent() != null) {
                        dismissPopupWindow(keyword);
                    }
                    return true;
                }
                return false;
            }
        });
    }

    private void initParams() {
        params = new WindowManager.LayoutParams();
        params.width = MATCH_PARENT;
        params.height = MATCH_PARENT;
        params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN //布滿屏幕愧捕,忽略狀態(tài)欄
                | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS //透明狀態(tài)欄
                | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; //透明虛擬按鍵欄
        params.format = PixelFormat.TRANSLUCENT;//支持透明
        params.gravity = Gravity.LEFT | Gravity.TOP;
    }

    /**
     * 將bitmap模糊虛化并設(shè)置給view background
     *
     * @param view
     * @param bitmap
     * @return 虛化后的view
     */
    private View getBlurView(View view, Bitmap bitmap) {
        Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() / 3, bitmap
                .getHeight() / 3, false);
        Bitmap blurBitmap = UIUtils.getBlurBitmap(activity, scaledBitmap, 5);
        view.setAlpha(0);
        view.setBackgroundDrawable(new BitmapDrawable(null, blurBitmap));
        alphaAnim(view, 0, 1, animDuration);
        return view;
    }

    /**
     * 彈出選項(xiàng)彈窗
     * 默認(rèn)從點(diǎn)擊位置彈出
     *
     * @param locationView
     */
    public void displayPopupWindow(View locationView) {
        displayPopupWindow(locationView, KEYWORD_LOCATION_CLICK);
    }

    /**
     * 彈出選項(xiàng)彈窗
     *
     * @param locationView 被點(diǎn)擊的view
     * @param keyword      彈出位置關(guān)鍵字
     */
    public void displayPopupWindow(View locationView, int keyword) {
        if (!isAniming) {
            isAniming = true;
            try {
                int[] point = new int[2];
                float x = 0;
                float y = 0;

                contentLayout.measure(0, 0);
                switch (keyword) {
                    case KEYWORD_LOCATION_CLICK:
                        //得到該view相對于屏幕的坐標(biāo)
                        locationView.getLocationOnScreen(point);
                        x = point[0] + locationView.getWidth() - contentLayout.getMeasuredWidth();
                        y = point[1] + locationView.getHeight();
                        break;
                    case KEYWORD_LOCATION_TOP:
                        x = 0;
                        y = 0;
                        break;
                    default:
                        break;
                }

                contentLayout.setX(x);
                contentLayout.setY(y);

                View decorView = activity.getWindow().getDecorView();
                Bitmap bitmap = UIUtils.viewToBitmap(decorView);//將view轉(zhuǎn)成bitmap
                View blurView = getBlurView(rootView, bitmap);//模糊圖片
                windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
                //將處理過的blurView添加到window
                windowManager.addView(blurView, params);

                //popupWindow動(dòng)畫
                popupAnim(contentLayout, 0.f, 1.f, animDuration, keyword, true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 消失popupWindow
     * 默認(rèn)從點(diǎn)擊處開始消失
     */
    public void dismissPopupWindow() {
        dismissPopupWindow(KEYWORD_LOCATION_CLICK);
    }

    /**
     * 消失popupWindow
     * @param keyword  消失位置關(guān)鍵字 KEYWORD_LOCATION_TOP:頂部彈出
     *                 KEYWORD_LOCATION_CLICK:點(diǎn)擊位置彈出
     */
    public void dismissPopupWindow(int keyword) {
        if (!isAniming) {
            isAniming = true;
            if (isDisplay) {
                popupAnim(contentLayout, 1.f, 0.f, animDuration, keyword, false);
            }
        }
    }

    /**
     * 設(shè)置透明度屬性動(dòng)畫
     *
     * @param view     要執(zhí)行屬性動(dòng)畫的view
     * @param start    起始值
     * @param end      結(jié)束值
     * @param duration 動(dòng)畫持續(xù)時(shí)間
     */
    private void alphaAnim(final View view, int start, int end, int duration) {
        ObjectAnimator.ofFloat(view, "alpha", start, end).setDuration(duration).start();
    }

    /**
     * popupWindow屬性動(dòng)畫
     *
     * @param view
     * @param start
     * @param end
     * @param duration
     * @param keyword
     * @param isToDisplay 顯示或消失 flag值
     */
    private void popupAnim(final View view, float start, final float end, int duration, final int
            keyword, final boolean isToDisplay) {
        ValueAnimator va = ValueAnimator.ofFloat(start, end).setDuration(duration);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                switch (keyword) {
                    case KEYWORD_LOCATION_CLICK:
                        view.setPivotX(view.getMeasuredWidth());
                        view.setPivotY(0);
                        view.setScaleX(value);
                        view.setScaleY(value);
                        view.setAlpha(value);
                        break;
                    case KEYWORD_LOCATION_TOP:
                        view.setPivotX(0);
                        view.setPivotY(0);
                        view.setScaleY(value);
                        break;
                    default:
                        break;
                }

            }
        });
        va.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                isAniming = false;
                if(isToDisplay) {//當(dāng)前為彈出popupWindow
                    isDisplay = true;
                    onPopupStateListener.onDisplay(isDisplay);
                }else{//當(dāng)前為消失popupWindow
                    try {
                        if (isDisplay) {
                            windowManager.removeViewImmediate(rootView);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    isDisplay = false;
                    onPopupStateListener.onDismiss(isDisplay);
                }
            }
        });
        va.start();
    }
}

現(xiàn)在看看實(shí)現(xiàn)效果奢驯。


這里寫圖片描述

點(diǎn)擊按鈕+頂部彈出PopupWindow界面+模糊背景效果

接下來的是最難的一個(gè)地方!4位妗瘪阁!
并不是!S寿恕管跺!騙你的!:探豁跑!哈哈,實(shí)際上前面的代碼也已經(jīng)寫的很清楚了泻云,我們這個(gè)頂部彈出的這個(gè)界面是個(gè)什么東西呢贩绕?沒錯(cuò);鸬摹!淑倾!就是一個(gè)ImageViewA蠛住!娇哆!鵝已E壤邸!碍讨!

OK治力,玩笑開完。要注意一點(diǎn)就是ImageView應(yīng)避免設(shè)置background而是應(yīng)該設(shè)置src勃黍,因?yàn)樵O(shè)置background可能會(huì)因?yàn)閳D片比例導(dǎo)致圖片拉伸失真宵统,當(dāng)然QQ頂部彈下來的肯定不是一個(gè)ImageView,這里也只是做一個(gè)效果覆获,實(shí)際應(yīng)用中自然可以根據(jù)需求去拓展马澈。

最后定義一個(gè)接口OnPopupStateListener 用于將PopupWindow狀態(tài)告知給TitleBar,然后TitleBar按鍵根據(jù)回調(diào)狀態(tài)給按鈕“+”設(shè)置屬性動(dòng)畫弄息。

  /**
     * popupWindow顯示和消失狀態(tài)變化接口
     */
    public interface OnPopupStateListener {
        /**
         * popupWindow狀態(tài)變化
         * @param isDisplay popupWindow當(dāng)前狀態(tài) true:顯示 false:消失
         */
        //        void onChange(boolean isDisplay);

        /**
         * popupWindow為顯示狀態(tài)
         */
        void onDisplay(boolean isDisplay);

        /**
         * popupWindow為消失狀態(tài)
         */
        void onDismiss(boolean isDisplay);
    }

TitleBar 接口實(shí)現(xiàn)以及按鈕動(dòng)畫

    private void initPopupWindow(final Activity context) {
        ImageView iv_popup_top = new ImageView(context);
        LayoutParams params = new LayoutParams(LayoutParams
                .MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        iv_popup_top.setLayoutParams(params);
        blurPopupWindow = new BlurPopupWindow(context, iv_popup_top,
                KEYWORD_LOCATION_TOP);
        blurPopupWindow.setOnPopupStateListener(new BlurPopupWindow.OnPopupStateListener() {
            @Override
            public void onDisplay(boolean isDisplay) {
                TitleBar.this.isDisplay = isDisplay;
            }

            @Override
            public void onDismiss(boolean isDisplay) {
                TitleBar.this.isDisplay = isDisplay;
                dismissAnim();
            }
        });
    }
    
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_add:
                if (onBarClicklistener != null) {
                    if (isDisplay) {
                        dismissPopupWindow();
                    } else {
                        displayPopupWindow(v);
                    }
                    onBarClicklistener.onBarClick(R.id.btn_add);
                }
                break;
            case R.id.btn_back:
                if (onBarClicklistener != null) {
                    onBarClicklistener.onBarClick(R.id.btn_back);
                }
                break;
        }
    }

    public void displayPopupWindow(View v) {
        displayAnim();
        blurPopupWindow.displayPopupWindow(v, KEYWORD_LOCATION_TOP);
    }

    public void dismissPopupWindow() {
        dismissAnim();
        blurPopupWindow.dismissPopupWindow(KEYWORD_LOCATION_TOP);
    }

    /**
     * Add按鈕逆時(shí)針轉(zhuǎn)90度
     */
    private void displayAnim() {
        ObjectAnimator.ofFloat(btn_add, "rotation", 0.f, -90.f).setDuration(500).start();
    }

    /**
     * Add按鈕瞬時(shí)間轉(zhuǎn)90度
     */
    private void dismissAnim() {
        ObjectAnimator.ofFloat(btn_add, "rotation", 0.f, 90.f).setDuration(500).start();
    }

貼上最終模擬器上運(yùn)行的效果


這里寫圖片描述

最后附上完整demo地址:https://github.com/Horrarndoo/parallaxListView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末痊班,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子摹量,更是在濱河造成了極大的恐慌涤伐,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缨称,死亡現(xiàn)場離奇詭異凝果,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)睦尽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門器净,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人骂删,你說我怎么就攤上這事掌动。” “怎么了宁玫?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵粗恢,是天一觀的道長。 經(jīng)常有香客問我欧瘪,道長眷射,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮妖碉,結(jié)果婚禮上涌庭,老公的妹妹穿的比我還像新娘。我一直安慰自己欧宜,他們只是感情好坐榆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冗茸,像睡著了一般席镀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夏漱,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天豪诲,我揣著相機(jī)與錄音,去河邊找鬼挂绰。 笑死屎篱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葵蒂。 我是一名探鬼主播交播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼刹勃!你這毒婦竟也來了堪侯?” 一聲冷哼從身側(cè)響起嚎尤,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤荔仁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后乏梁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡炭剪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疚沐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亮蛔。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梯嗽,死狀恐怖绵估,靈堂內(nèi)的尸體忽然破棺而出形入,到底是詐尸還是另有隱情渺杉,我是刑警寧澤耳舅,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布天梧,位于F島的核電站蚯妇,受9級特大地震影響箩言,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一茫打、第九天 我趴在偏房一處隱蔽的房頂上張望轮洋。 院中可真熱鬧开财,春花似錦薇搁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筷畦。三九已至,卻和暖如春逆航,著一層夾襖步出監(jiān)牢的瞬間鼎文,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工因俐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漂问,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓女揭,卻偏偏與公主長得像蚤假,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子吧兔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容