單詞拖動(dòng)

不知道在哪里看到過(guò)一個(gè)需求,具體的也記不清了瞄沙,有點(diǎn)久了,現(xiàn)在抽空寫下效果慌核。
簡(jiǎn)單需求如下距境,給一個(gè)單詞,給一個(gè)句子垮卓,然后把單詞拖動(dòng)到正確的位置垫桂。拖動(dòng)結(jié)束,判斷下位置是否正確粟按,正確的話單詞顯示綠色诬滩,不正確的話單詞顯示紅色。然后顯示正確的句子以及釋義灭将。
注:如果用戶在無(wú)效范圍內(nèi)松開(kāi)手指疼鸟,那么還原即可,還可以重新拖動(dòng)庙曙。

長(zhǎng)按開(kāi)始拖動(dòng)空镜。
20180906_132653.gif

最開(kāi)始的思路

最開(kāi)始我是把選項(xiàng)單詞和下邊的句子分開(kāi)弄做2個(gè)控件的,單獨(dú)處理了上邊的觸摸事件捌朴,完事修改下邊的控件吴攒,可發(fā)現(xiàn)如果對(duì)下邊的控件進(jìn)行invalidate的話,上邊拖動(dòng)的view的觸摸事件就沒(méi)了砂蔽。

新的思路

平時(shí)用recyclerview洼怔,這玩意感覺(jué)就是萬(wàn)能的啊,那何不用這個(gè)來(lái)寫左驾,大家就是一個(gè)view了镣隶,事件也好處理泽台。

第一步,先自定義一個(gè)LayoutManager 把第一幅圖的效果弄出來(lái)

這個(gè)還是比較簡(jiǎn)單的矾缓。第一個(gè)item單獨(dú)一行居中怀酷,上下留點(diǎn)間距,完事從第二個(gè)item開(kāi)始大家就按順序一排一排的添加即可嗜闻。
暫時(shí)不考慮滑動(dòng)蜕依,復(fù)用的問(wèn)題,因?yàn)檫@里的需求基本就是一個(gè)頁(yè)面顯示的琉雳,也沒(méi)有滾動(dòng)和復(fù)用的必要样眠。

代碼如下WordsLayoutManager.kt

import android.support.v7.widget.RecyclerView
import android.view.ViewGroup

class WordsLayoutManager:RecyclerView.LayoutManager(){
    override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
        return RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
        super.onLayoutChildren(recycler, state)
        if(state.itemCount==0){
            removeAndRecycleAllViews(recycler)
        }
        if (childCount == 0 && state.isPreLayout) {
            return
        }
        detachAndScrapAttachedViews(recycler)
        var left=paddingLeft
        var top=paddingTop
        repeat(state.itemCount){
            val child=recycler.getViewForPosition(it)
            addView(child)
            measureChildWithMargins(child,0,0)
            val childWidth=getDecoratedMeasuredWidth(child)
            val childHeight=getDecoratedMeasuredHeight(child)
            if(it==0){
                left=(width-childWidth)/2//第一個(gè)item居中顯示
                top=paddingTop+childHeight*2//默認(rèn)第一個(gè)item上下的間距都是item高度的2倍
            }else if(it==1){
                left=paddingLeft
                top=paddingTop+childHeight*5
            }else{
                if(left+childWidth>width-paddingRight){//開(kāi)始添加之前得先判斷下添加以后是否跑到屏幕外邊了,如果超出了翠肘,那就換行展示
                    left=paddingLeft
                    top+=childHeight //我們這里文字是單行的檐束,所以一行多個(gè)元素,高度是一樣的束倍,就不處理了被丧。
                }
            }
            layoutDecoratedWithMargins(child,left,top,left+childWidth,top+childHeight)
            if(it>0){
                left+=childWidth //添加完child以后,計(jì)算新的left位置
            }
        }

    }

}

使用起來(lái)也就比較簡(jiǎn)單了绪妹,adapter自己寫甥桂,item弄個(gè)textview就完事了。

 layoutManager=WordsLayoutManager()
 addItemDecoration(ItemDecorationSpace())
//decoration就簡(jiǎn)單寫了個(gè)邮旷,我手機(jī)1dp=1px,所以偷懶不考慮換算了黄选,直接數(shù)字就上了。
 outRect.left=5
        outRect.right=5
        outRect.bottom=20

好了婶肩,第一步展示就完工了办陷,下邊考慮拖動(dòng)

拖動(dòng),就用系統(tǒng)的ItemTouchHelper

注釋都很清楚了律歼,簡(jiǎn)單說(shuō)下數(shù)據(jù)民镜,我是假設(shè)把所有單詞一次都給到一個(gè)數(shù)組里,第一個(gè)單詞就是可以拖動(dòng)的選項(xiàng)苗膝,然后還得給正確答案應(yīng)該插在哪個(gè)位置殃恒,這是按照給的單詞來(lái)講的。
比如給了單詞 a,b,c,d,e 其中第一個(gè)a就是要拖動(dòng)的辱揭,答案如果是3的話离唐,那么就是插入c和d之間。具體邏輯實(shí)際情況修改问窃。拖動(dòng)邏輯都在ItemTouchHelper的callback里了亥鬓。

最后的代碼如下

單詞實(shí)體類,就一個(gè)word和一個(gè)boolean值【用來(lái)判斷是否是插入的數(shù)據(jù)】域庇。

data class WordBean(var word:String,var inserted:Boolean=false)
import android.graphics.Canvas
import android.graphics.Color
import android.os.Bundle
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
import android.view.View
import android.widget.TextView
import com.charliesong.demo0327.base.BaseActivity
import com.charliesong.demo0327.base.BaseRvAdapter
import com.charliesong.demo0327.base.BaseRvHolder
import com.charliesong.demo0327.R
import kotlinx.android.synthetic.main.activity_words.*
import java.util.*

/**
 * Created by charlie.song on 2018/4/28.
 */
class ActivityWords : BaseActivity() {
    var rightPosition = 3;//正確答案的位置嵌戈,假設(shè)是這個(gè)覆积。
    var choice = -1;//這是松開(kāi)手指的時(shí)候最終選擇的位置索引
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_words)


        var wordsOriginal = arrayListOf<WordBean>()
        wordsOriginal.add(WordBean("He"))
        wordsOriginal.add(WordBean("must"))
        wordsOriginal.add(WordBean("been"))
        wordsOriginal.add(WordBean("single"))
        wordsOriginal.add(WordBean("activity"))
        wordsOriginal.add(WordBean("fragment"))
        wordsOriginal.add(WordBean("honey"))
        wordsOriginal.add(WordBean("restaurant"))
        rv_words.apply {
            layoutManager = WordsLayoutManager()
            addItemDecoration(ItemDecorationSpace())
            adapter = object : BaseRvAdapter<WordBean>(wordsOriginal) {
                override fun getLayoutID(viewType: Int): Int {
                    return R.layout.item_simple_word
                }

                override fun onBindViewHolder(holder: BaseRvHolder, position: Int) {
                    holder.itemView.layoutParams = RecyclerView.LayoutParams(-2, -2)
                    var bean = getItemData(position)
                    holder.getView<TextView>(R.id.tv_word).apply {
                        text = bean.word
                        visibility = if (bean.inserted) View.INVISIBLE else View.VISIBLE
                        if (choice == position) {//choice是我們最終選擇的位置,這里處理下顏色熟呛,對(duì)的話為綠色宽档,錯(cuò)的話為紅色,
                            visibility = View.VISIBLE
                            setBackgroundColor(if (rightPosition == position) Color.GREEN else Color.RED)
                        }
                    }
                }
            }
        }

        var helper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
            var temp = -1
            override fun onSwiped(viewHolder: RecyclerView.ViewHolder?, direction: Int) {

            }

            //這里用來(lái)判斷處理哪些情況
            override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
                var position = viewHolder.adapterPosition
                if (position != 0 || wordsOriginal[position].inserted) {//
                    return 0   //只有第一個(gè)可以拖動(dòng)庵朝,另外拖動(dòng)過(guò)之后也不能再次拖動(dòng)了吗冤,其實(shí)第一個(gè)也就隱藏了
                }
                val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.START or ItemTouchHelper.END  //這個(gè)是拖動(dòng)的flag
                return makeMovementFlags(dragFlags, 0)
            }

            //拖拽的時(shí)候會(huì)不停的回掉這個(gè)方法,我們?cè)谶@里做的就是交換對(duì)應(yīng)的數(shù)據(jù)
            override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
                var oldPositioon = viewHolder.adapterPosition
                var newPosition = target.adapterPosition
                if (temp < 0) {//首次拖動(dòng)到有效范圍的時(shí)候temp肯定是-1拉九府,這時(shí)候我們?cè)谕蟿?dòng)的位置添加一條選項(xiàng)數(shù)據(jù)椎瘟,也就是第一條數(shù)據(jù)。
                    wordsOriginal.add(newPosition, wordsOriginal.get(0).apply { inserted = true })//為啥inserted=true侄旬,是為了使這個(gè)item不可見(jiàn)肺蔚,只用來(lái)占位
                    recyclerView.adapter.notifyItemInserted(newPosition)
                }
                if (temp == newPosition) {//在同一個(gè)位置來(lái)回晃悠,啥也不干
                    return false
                }
                if (temp > 0) {//這個(gè)temp其實(shí)就是我們添加的那條數(shù)據(jù)儡羔,用他來(lái)代替第一條數(shù)據(jù)來(lái)移動(dòng)
                    Collections.swap(wordsOriginal, temp, newPosition)
                    recyclerView.adapter.notifyItemMoved(temp, newPosition)
                }
                temp = newPosition
                return true
            }

            //使用kotlin的時(shí)候得注意宣羊,這個(gè)viewHolder是可能為空的,當(dāng)state為Idle的時(shí)候
            override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
                super.onSelectedChanged(viewHolder, actionState)
                viewHolder?.run {
                    if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
                        itemView.setBackgroundColor(Color.RED); //正在拖動(dòng)的item弄成紅色
                    }
                }
            }

            override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
                super.clearView(recyclerView, viewHolder)
                viewHolder.itemView.setBackgroundColor(0);//正在拖動(dòng)的item顏色還原
                if(!wordsOriginal[0].inserted){
                    recyclerView.adapter.notifyItemChanged(0)
                }
            }

            //手指拖動(dòng)的時(shí)候isCurrentlyActive是true笔链,松開(kāi)手指的時(shí)候成為false段只,可以在false的時(shí)候判斷下當(dāng)前item的位置是否在有效位置,dY就是y軸方向移動(dòng)的距離鉴扫,往下是正的
            override fun onChildDraw(c: Canvas?, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
                //我們只判斷y的范圍是否在有效范圍內(nèi),temp大于0表示曾經(jīng)移動(dòng)到有效范圍內(nèi)
                if (temp > 0 && !isCurrentlyActive) {
                    val firstChild = recyclerView.getChildAt(0)
                    val secondTop = recyclerView.getChildAt(1).top
                    val lastBottom = recyclerView.getChildAt(recyclerView.childCount - 1).bottom
                    if (firstChild.bottom + dY < secondTop || firstChild.top + dY > lastBottom) {
                        //有效范圍之外松手澈缺,那么還原數(shù)據(jù)坪创,不做判斷
                        wordsOriginal.removeAt(temp)
                        recyclerView.adapter.notifyItemRemoved(temp)
                        wordsOriginal[0].inserted = false;
                        //更新操作放到clearView里,因?yàn)檫@時(shí)候第一個(gè)view正在進(jìn)行回到原始位置的動(dòng)畫(huà)姐赡。
                    } else {
                        choice = temp
                        recyclerView.adapter.notifyDataSetChanged()
                        //最終的choice和正確的rightPosition比較下莱预,對(duì)錯(cuò)之后要感謝啥,自己處理
                        showToast("選擇${if (choice == rightPosition) "正確" else "錯(cuò)誤"}")
                    }
                    temp = -1
                }
            }

        })
        helper.attachToRecyclerView(rv_words)

    }

}

最后附上有效位置判斷圖解

第一個(gè)item的bottom加上移動(dòng)的距離 小于第二個(gè)item的top项滑,很明顯就是在句子上邊了依沮。
第一個(gè)item的top加上移動(dòng)的距離 大于 最后一個(gè)item的bottom,很明顯跑到句子最下邊去了枪狂。


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末危喉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子州疾,更是在濱河造成了極大的恐慌辜限,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件严蓖,死亡現(xiàn)場(chǎng)離奇詭異薄嫡,居然都是意外死亡氧急,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門毫深,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吩坝,“玉大人,你說(shuō)我怎么就攤上這事哑蔫《で蓿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵鸳址,是天一觀的道長(zhǎng)瘩蚪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)稿黍,這世上最難降的妖魔是什么疹瘦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮巡球,結(jié)果婚禮上言沐,老公的妹妹穿的比我還像新娘。我一直安慰自己酣栈,他們只是感情好险胰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著矿筝,像睡著了一般起便。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窖维,一...
    開(kāi)封第一講書(shū)人閱讀 51,488評(píng)論 1 302
  • 那天榆综,我揣著相機(jī)與錄音,去河邊找鬼铸史。 笑死鼻疮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的琳轿。 我是一名探鬼主播判沟,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼崭篡!你這毒婦竟也來(lái)了挪哄?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤媚送,失蹤者是張志新(化名)和其女友劉穎中燥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體塘偎,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疗涉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年拿霉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咱扣。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绽淘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闹伪,到底是詐尸還是另有隱情沪铭,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布偏瓤,位于F島的核電站杀怠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏厅克。R本人自食惡果不足惜赔退,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望证舟。 院中可真熱鬧硕旗,春花似錦、人聲如沸女责。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)抵知。三九已至墙基,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刷喜,已是汗流浹背碘橘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吱肌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓仰禽,卻偏偏與公主長(zhǎng)得像氮墨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吐葵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,129評(píng)論 25 707
  • 每天的學(xué)習(xí)記錄规揪,可能有的地方寫的不對(duì),因?yàn)閯倢W(xué)温峭,以后發(fā)現(xiàn)錯(cuò)的話會(huì)回來(lái)改掉整體流程 https://develope...
    有點(diǎn)健忘閱讀 4,671評(píng)論 0 7
  • 用到的組件 1猛铅、通過(guò)CocoaPods安裝 2、第三方類庫(kù)安裝 3凤藏、第三方服務(wù) 友盟社會(huì)化分享組件 友盟用戶反饋 ...
    SunnyLeong閱讀 14,616評(píng)論 1 180
  • 遇?瑜伽 一個(gè)人可以走的很快 但一群人一起走會(huì)走的更遠(yuǎn) 請(qǐng)珍惜身邊陪你一...
    夢(mèng)茹YOGA閱讀 97評(píng)論 0 0
  • 【環(huán)境】公歷2018.4.1 農(nóng)歷2018.2.16 星期日 晴 最高溫度29℃ 最低溫度11℃ 南風(fēng)3級(jí) 蘋果花...
    贠大師閱讀 98評(píng)論 0 0