Kotlin在Fragment中監(jiān)聽手勢(shì)并轉(zhuǎn)場(chǎng)

引言


先看以下將要實(shí)現(xiàn)目標(biāo)的效果


預(yù)覽

解析布局:
1、啟動(dòng)頁由于類型不同檩坚,因此選用fragment顯示
2不翩、fragment根布局采用的VideoViewIjk
3麦轰、底部閃爍的上三角MotionalArrowView
4、指示器-IndicatorView
5闯睹、幕布式TextView-CurtainTextView

3巷挥、4烹卒、5都是由RelativeLayout包裹

整個(gè)頁面能夠識(shí)別左右上三個(gè)方向的手勢(shì)章咧,根據(jù)滑動(dòng)的方向選用不同的轉(zhuǎn)場(chǎng)動(dòng)畫倦西。

仔細(xì)觀察的人是否能夠察覺在第一頁左滑時(shí)與原作的不同呢?這是因?yàn)樵髦惺褂昧薞iewPager(嘻嘻別問我怎么知道的)赁严,接下來開始講述編碼歷程扰柠。

正文


順序按交互與否排序,IndicatorView和CurtainTextView屬于有用戶交互疼约,MotionalArrowView則沒有卤档,最后是交互的實(shí)現(xiàn)GestureDetector

  • MotionalArrowView

實(shí)現(xiàn)思路是自定義VireGroup將兩個(gè)三角形上下擺放,設(shè)置屬性動(dòng)畫改變其透明度程剥。

中途遇到的坑:由于圖素選取時(shí)尺寸大于控件顯示的尺寸劝枣,導(dǎo)致了自定義控件內(nèi)部ImageView不按約束顯示,所以在使用此控件時(shí)要將其設(shè)置成寬小于高的矩形织鲸。

    fun initView() {
        upImageView = ImageView(context)
        downImageView = ImageView(context)

        upImageView.setImageDrawable(ContextCompat.getDrawable(context, R.mipmap.ic_action_up))
        downImageView.setImageDrawable(ContextCompat.getDrawable(context, R.mipmap.ic_action_up))

        //如果是正方形舔腾,則看不出效果,因?yàn)閳D片太大了
        var params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
        params.addRule(ALIGN_PARENT_BOTTOM)

        addView(upImageView)
        addView(downImageView, params)
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        if (!isInEditMode) {
            showAnimation()
        }
    }

    fun showAnimation() {
        var upAnimator = ObjectAnimator.ofFloat(upImageView, "alpha", 0.3f, 1f, 0.3f)
        var downAnimator = ObjectAnimator.ofFloat(downImageView, "alpha", 0.3f, 1f, 0.3f)
        upAnimator.duration = 1000
        downAnimator.duration = 1000
        upAnimator.startDelay = 500

        var animatorSet = AnimatorSet()
        animatorSet.playTogether(upAnimator, downAnimator)
        animatorSet.addListener(object : Animator.AnimatorListener {

            override fun onAnimationEnd(animation: Animator?) {
                animatorSet.startDelay = 500
                animatorSet.start()
            }

            override fun onAnimationRepeat(animation: Animator?) {
            }

            override fun onAnimationCancel(animation: Animator?) {
            }

            override fun onAnimationStart(animation: Animator?) {
            }
        })
        animatorSet.start()
    }
  • IndicatorView

這個(gè)就比較簡(jiǎn)單了搂擦,用LinearLayout包裹ImageView琢唾,切換時(shí)更換ImageView的Drawable。

這里踩了一個(gè)kotlin的坑:在typedArray.getDrawable()時(shí)盾饮,如果控件并沒有設(shè)置此屬性而是采用默認(rèn)值

        //定義
        private var normalBG: Drawable

        //如果這么寫
        normalBG = typedArray.getDrawable(R.styleable.IndicatorView_indicatorView_normal)
        if (normalBG == null) {
            normalBG = ContextCompat.getDrawable(context, R.mipmap.ic_indicator_normal)
        }

        //結(jié)果
Caused by: java.lang.IllegalStateException: typedArray.getDrawable(R…iew_indicatorView_normal) must not be null

因?yàn)槎xnormalBG時(shí)認(rèn)定不為空,所以當(dāng)typedArray.getDrawable()取空值時(shí)報(bào)異常

如果定義其為private var normalBG: Drawable?則不報(bào)異常

因?yàn)槲叶x的normalBG有默認(rèn)值懒熙,肯定不為空所以改了如下寫法(究其原因還是kotlin對(duì)于空指針異常的把控丘损,再加上自己kotlin寫法的不熟練)

    init {
        gravity = Gravity.CENTER
        var typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicatorView)
        contentMargin = typedArray.getDimensionPixelSize(R.styleable.IndicatorView_indicatorView_margin, 15)

        var tempBG = typedArray.getDrawable(R.styleable.IndicatorView_indicatorView_normal)
        if (tempBG != null) {
            normalBG = tempBG
        } else {
            normalBG = ContextCompat.getDrawable(context, R.mipmap.ic_indicator_normal)
        }
        tempBG = typedArray.getDrawable(R.styleable.IndicatorView_indicatorView_checked)
        if (tempBG != null) {
            selectBG = tempBG
        } else {
            selectBG = ContextCompat.getDrawable(context, R.mipmap.ic_indicator_selected)
        }

        setSize(typedArray.getInt(R.styleable.IndicatorView_indicatorView_count, 0))
        typedArray.recycle()
    }

    fun setSize(size: Int) {
        removeAllViews()
        for (i in 0 until size) {
            var imageView = ImageView(context)
            var params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
            params.leftMargin = contentMargin
            imageView.scaleType = ImageView.ScaleType.CENTER
            if (i == 0) {
                imageView.setImageDrawable(selectBG)
            } else {
                imageView.setImageDrawable(normalBG)
            }
            addView(imageView, params)
        }
    }

    fun select(position: Int) {
        if (position < childCount) {
            for (i in 0 until childCount) {
                var imageView: ImageView = getChildAt(i) as ImageView
                if (position == i) {
                    imageView.setImageDrawable(selectBG)
                } else {
                    imageView.setImageDrawable(normalBG)
                }
            }
        }
    }
  • CurtainTextView

這個(gè)就比較叼了!最開始我自定義了TypeTextView控件工扎,通過ValueAnimator.ofInt(0, content.length)不斷setText徘钥,能夠?qū)崿F(xiàn)動(dòng)態(tài)打字的效果,但其并不能達(dá)到預(yù)期的動(dòng)畫效果肢娘。因?yàn)槊恳淮蔚膕etText呈础,TextView本身都要重新測(cè)算一下自身,結(jié)果就像是一個(gè)不斷變長(zhǎng)的矩形橱健。

而我想要的則是像將矩形上的遮布逐漸揭開的效果而钞。

這讓我想到了之前有一篇介紹Span的文章文中雖然效果圖和代碼并不完全匹配,細(xì)讀一下代碼還是很有幫助的拘荡。于是有了一下代碼

    init {
        animator = ObjectAnimator.ofFloat(this, "textAlpha", 0f, 1f)
        animator.duration = 1000
        animator.addUpdateListener { animation -> text = spannableString }
    }

    fun setContentText(string: String) {
        spannableString = SpannableString(string)
        spanList = ArrayList()
        for (i in 0 until string.length) {
            var span = MutableForegroundColorSpan()
            spanList.add(span)
            spannableString.setSpan(span, i, i + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        }
        animator.start()
    }


    class MutableForegroundColorSpan : CharacterStyle(), UpdateAppearance {
        var alpha = 0

        override fun updateDrawState(tp: TextPaint) {
            tp.alpha = alpha
        }

    }

    fun setTextAlpha(alpha: Float) {
        var size = spanList.size
        var total = size * alpha
        for (i in 0 until size) {
            var span = spanList.get(i)
            if (total >= 1) {
                span.alpha = 255
                --total
            } else {
                span.alpha = (255 * total).toInt()
                total = 0f
            }
        }
    }

其原理是將要設(shè)置的文字全部拆成字符臼节,并對(duì)每個(gè)字符設(shè)置CharacterStyle,通過ObjectAnimator改變每個(gè)字符CharacterStyle的透明度。效果就像是原本一行透明的文字逐漸地從第一個(gè)字符慢慢顯示出來

  • GestureDetector

終于到了文章標(biāo)題的主旨网缝,由于在fragment中無法重寫onTouchEvent所以將重任交給了宿主Activity巨税。

(其實(shí)也可以將GestureDetector放到布局中的View上,由于kotlin還是不太順手所以一直都報(bào)View的空指針粉臊,現(xiàn)在想想應(yīng)該是調(diào)用的時(shí)間不對(duì)草添,無法在onCreate和onCreateView附近的生命周期調(diào)用)

        gestureDetector = GestureDetector(activity, object : GestureDetector.OnGestureListener {
            override fun onShowPress(e: MotionEvent?) {
            }

            override fun onSingleTapUp(e: MotionEvent?): Boolean {
                return false
            }

            override fun onDown(e: MotionEvent?): Boolean {
                return false
            }

            override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
                var yDifference = e2.y - e1.y
                var xDifference = e2.x - e1.x
                if (Math.abs(xDifference) > Math.abs(yDifference)) {//橫向
                    if (xDifference > 0) {//right
                        setPosition(--currentPosition)
                    } else {
                        setPosition(++currentPosition)
                    }
                } else {//縱向
                    if (yDifference > 0) {//down


                    } else {
                        goMainLeft(false)
                    }
                }

                return true
            }

            override fun onLongPress(e: MotionEvent?) {
            }

            override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
                return false
            }
        })

        //交接重任
        (activity as SplashActivity).gestureDetector = gestureDetector

關(guān)鍵方法是onFling()其中參數(shù)e1、e2分別代表滑動(dòng)的起始點(diǎn)和結(jié)束點(diǎn)扼仲。以手機(jī)屏幕左上角為原點(diǎn)远寸,向右x軸逐漸增加,向下y軸逐漸增加犀盟,以此為依據(jù)而晒,y值相同時(shí)e2.x > e1.x表示右滑、x值相同時(shí)e2.y > e1.y表示下滑

    //Activity
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (gestureDetector != null) {
            return gestureDetector.onTouchEvent(event)
        }
        return super.onTouchEvent(event)
    }

至此手勢(shì)已經(jīng)獲取到了阅畴,轉(zhuǎn)場(chǎng)的代碼與java并無二致

//由于不會(huì)用到退場(chǎng)動(dòng)畫倡怎,所以就一樣了
overridePendingTransition(R.anim.slide_in_right, R.anim.slide_in_right)
//R.anim.slide_in_right
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromXDelta="50.0%p"http://x軸在屏幕50%的地方開始 p代表parent
    android:interpolator="@android:anim/decelerate_interpolator"
    android:toXDelta="0.0" />//在x軸0點(diǎn)處結(jié)束即屏幕最左邊
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市贱枣,隨后出現(xiàn)的幾起案子监署,更是在濱河造成了極大的恐慌,老刑警劉巖纽哥,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钠乏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡春塌,警方通過查閱死者的電腦和手機(jī)晓避,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來只壳,“玉大人俏拱,你說我怎么就攤上這事『鹁洌” “怎么了锅必?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)惕艳。 經(jīng)常有香客問我搞隐,道長(zhǎng),這世上最難降的妖魔是什么远搪? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任劣纲,我火速辦了婚禮,結(jié)果婚禮上终娃,老公的妹妹穿的比我還像新娘味廊。我一直安慰自己蒸甜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布余佛。 她就那樣靜靜地躺著柠新,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辉巡。 梳的紋絲不亂的頭發(fā)上恨憎,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音郊楣,去河邊找鬼憔恳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛净蚤,可吹牛的內(nèi)容都是我干的钥组。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼今瀑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼程梦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起橘荠,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤屿附,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后哥童,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挺份,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年贮懈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匀泊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朵你,死狀恐怖探赫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情撬呢,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布妆兑,位于F島的核電站魂拦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏搁嗓。R本人自食惡果不足惜芯勘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腺逛。 院中可真熱鬧荷愕,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至荐类,卻和暖如春怖现,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玉罐。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工屈嗤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吊输。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓饶号,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親季蚂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茫船,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,332評(píng)論 25 707
  • 效果圖: Github鏈接:https://github.com/boycy815/PinchImageView ...
    CQ_TYL閱讀 2,223評(píng)論 0 0
  • 原文地址:http://www.android100.org/html/201606/06/241682.html...
    AFinalStone閱讀 947評(píng)論 0 1
  • 一、概述當(dāng)用戶觸摸屏幕的時(shí)候癣蟋,會(huì)產(chǎn)生許多手勢(shì)透硝,例如down,up疯搅,scroll濒生,filing等等。一般情況下幔欧,我們...
    GB_speak閱讀 20,737評(píng)論 1 18
  • 那一年 她卸下牙套后就沒有笑過了 本不該這么美的 現(xiàn)在想想還心有余悸 就像最黑暗的路 人們看不見前方 人們磕磕碰碰...
    留子堯閱讀 196評(píng)論 1 5