摘要:二這個單詞隨手勢的移動單詞塊相比較上面流式布局的實現,這個就相對復雜多了。在這個模塊中,我們需要實現以下邏輯。
前言
前兩天在改完APP的一些bug之后逛了一下貼吧,在Android開發吧中很驚喜的發現了一個朋友在尋求幫助。為什么說驚喜呢?因為現在這個貼吧已經淪為了接畢設課設的重災區,少有人在這里討論技術了。話說回來,這位朋友的問題是這樣的。
看到之后我覺得還是挺有意思的,加上工作也不是特別忙,就試著做了一下,下面是做成的效果。
實現思路每次得到一個新的需求的時候,要將一個大的需求進行劃分,劃分成主要的和次要的小需求,在這個大需求里面,“請將卡片移動到正確位置”,“跳過此題”和最后正確答案的顯示都是非常容易實現的小需求,先不管,除此之外有三個重點:
一、流式布局
二、“not”這個單詞隨手勢的移動——單詞塊
三、將單詞插入到原本的句子中
解決了這三點,基本也就實現了這個大需求了。
這個流式布局主要是為了承載題干的,當然使用RecyclerView+LayoutManager來實現是最簡單的,這里我使用的是xiangcman/LayoutManager-FlowLayout,用這個LayoutManager可以很輕松的實現流式布局來承載題干的內容。
二、“not”這個單詞隨手勢的移動——單詞塊相比較上面流式布局的實現,這個就相對復雜多了。主要考察的點是View的事件處理和坐標位置換算。我們主要監聽“單詞塊”的setOnTouchListener事件,然后處理MotionEvent.ACTION_MOVE事件,讓“單詞塊“隨著我們的手指移動。在這里,我們需要介紹下幾個重要的概念:
event.getRawX() //獲取相對于手機屏幕左上角的距離
event.getX() //獲取以被監聽事件控件為坐標系的離控件左上角的距離
view.getX() //獲取view相對于其父控件的位置
具體如下圖所示:
要想單詞塊能夠隨著我們的手指移動,我們需要獲取你當前手指指尖的位置,然后將單詞塊移動到你手指指尖的位置,然后通過view.setX(x)和view.setY(y)來設置view的位置,我們通過event.getRawX()和event.getRawY()來獲取我們當前手指在整個屏幕中的位置,view.setX(x)中的x是相對于他的父容器的,那么坐標的轉換就是一個大問題,如下圖所示:
所以最終view.setX(H.x)和view.setY(H.y),這樣就能實現”單詞塊“隨著手指指尖移動了,代碼如下
flow_text.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if ( event.getAction() == MotionEvent.ACTION_DOWN ) { //記錄手指指尖的位置和代詞塊左上角的x和y的值 firstClickX = event.getX(); firstClickY = event.getY(); //記錄單詞塊父容器和手機屏幕左上角的x和y的值 tempX = event.getRawX() - event.getX() - v.getX(); tempY = event.getRawY() - event.getY() - v.getY(); } else if ( event.getAction() == MotionEvent.ACTION_MOVE ) { //移動的時候 float positionX = event.getRawX() - firstClickX - tempX; float positionY = event.getRawY() - firstClickY - tempY; v.setX(positionX); v.setY(positionY); } return false; } });三、將單詞插入到原本的句子中
這個是最難實現的,也是最復雜的。在這個模塊中,我們需要實現以下邏輯。“單詞塊”移動到”題干“附近的時候,要開始計算當前“單詞塊”的中心點和”題干“中的哪兩個單詞的中間的”縫“最近,然后在這個”縫“所在的位子插入一個沒有內容的空格子,以提示用戶你將插入到這個位置,當“單詞塊”遠離題干的時候,不再計算位置;然后在釋放”單詞塊“的時候,如果是在”題干“附近釋放的時候(也就是有提示框出現的時候),將這個單詞插入到剛剛的那個”縫“的位置,然后給出答題的結果,是放對了還是放錯了,否則就是放棄本次答題,將“單詞塊”放回原來的位置。敘述起來很復雜,其實跟場景結合起來,還是很好理解的。
來,我們依然是各個擊破!
檢測“單詞塊”是否移動到”題干“附近
我們可以計算出“單詞塊”的中心點,然后計算出當前”題干“(也就是RecyclerView)的位置,如果“單詞塊”的中心點在”題干“的范圍內,那么就代表進入了要監聽的范圍了。代碼如下:
flow_text.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if ( event.getAction() == MotionEvent.ACTION_DOWN ) { //記錄手指指尖的位置和代詞塊左上角的x和y的值 firstClickX = event.getX(); firstClickY = event.getY(); //記錄單詞塊父容器和手機屏幕左上角的x和y的值 tempX = event.getRawX() - event.getX() - v.getX(); tempY = event.getRawY() - event.getY() - v.getY(); } else if ( event.getAction() == MotionEvent.ACTION_MOVE ) { //移動的時候 float positionX = event.getRawX() - firstClickX - tempX; float positionY = event.getRawY() - firstClickY - tempY; v.setX(positionX); v.setY(positionY); //被移動塊的中點 int centerX = ( int ) (positionX + mViewWidth / 2); int centerY = ( int ) (positionY + mViewHeight / 2); //rvY是RecyclerView距離頂部的距離rvHeight是RecyclerView的高度 if ( centerY > rvY && centerY < rvHeight + rvY ) { //在范圍內了 } else { //不在范圍內了 } } return false; } });
計算當前“單詞塊”的中心點和”題干“中的哪兩個單詞的中間的”縫“最近
其實這里有兩種思路,一種通過RecyclerView的適配器獲取到每個item的位置信息,然后計算出兩個item的中間位置,將所有的這些中間位置保存起來,在分別計算“單詞塊”的中心點和這些中間位置的距離,然后再處理,不過用這種方式需要考慮item換行之后中心點計算的問題(由于我沒有使用這種方式,對這個預期會出現的問題也沒有多加思考);還有一種是在創建題干的時候使用多類型的適配器,在每個單詞中間插入一個占位置的”空格“,這樣就可以直接獲取到這個”空格“的位置作為參照點,同時,這個空格還可以直接給用戶提示位置,一舉兩得。我這里就是用的第二種方式。
找出最近的”縫“
//找出最近的點 只找沒有內容的格子 就是占位格子 private ItemPositionModel findPoint() { //沒有數據直接返回 if ( itemList.isEmpty() ) return null; double distance = Math.sqrt(Math.pow((center.x - itemList.get(0).getCenter().x), 2) + Math.pow((center.y - itemList.get(0).getCenter().y), 2)); int index = 0; for ( int i = 1; i < itemList.size(); i++ ) { if ( i % 2 == 0 ) { double temp = Math.sqrt(Math.pow((center.x - itemList.get(i).getCenter().x), 2) + Math.pow((center.y - itemList.get(i).getCenter().y), 2)); if ( temp <= distance ) { distance = temp; index = i; } } } return itemList.get(index); }
找到這個”縫“之后,保存這個”縫“的下標,刷新適配器,在”縫“這個下標處顯示那個用于提示的空格子。
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { ShowItem showItem = list.get(position); if ( showItem != null ) if ( showItem.getType() == 0 ) { //正文內容 ...... } else { //currSelectIndex是縫的下標 if ( currSelectIndex == position ) { (( MyHolderDivider ) holder).tv_divider.setVisibility(View.VISIBLE); } else { (( MyHolderDivider ) holder).tv_divider.setVisibility(View.GONE); } } }
釋放”單詞塊“的時候
在釋放”單詞塊“的時候,我們需要判斷當前是否還在范圍內,如果是在范圍內,就在”縫“的地方插入”單詞塊“內部的單詞值,然后隱藏掉”單詞塊“,否則,隱藏剛剛用于提示的空格子并將”單詞塊“移動到之前的位置。
//抬起手指的一瞬間 if ( event.getAction() == MotionEvent.ACTION_UP ) { //如果在RecyclerView的范圍內才處理 否則回退到原地 if ( isInArea ) { //添加成功 移除之前的視圖 v.setVisibility(View.GONE); //檢查并設置結果 最好提取出來 ShowItem result = new ShowItem((( TextView ) v).getText().toString(), 0); if ( rightIndex == currSelectIndex ) { //正確 result.setIsRight(1); } else { //錯誤 result.setIsRight(2); } list.add(currSelectIndex + 1, result); list.add(currSelectIndex + 2, new ShowItem("", 1)); } else { //未成功添加抬起的時候回歸原地 v.setX(firstX); v.setY(firstY); } //重置位置 currSelectIndex = -1; flowAdapter.notifyDataSetChanged(); }
下面整個是多類型的適配器的代碼,由于比較簡單就寫的比較隨意,沒有多去封裝啥的:
class FlowAdapter extends RecyclerView.Adapter{ private List list; public FlowAdapter(List list) { this.list = list; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if ( viewType == 0 ) { //正文內容類型 return new MyHolder(View.inflate(MainActivity.this, R.layout.flow_item, null)); } else { //占位符類型 return new MyHolderDivider(View.inflate(MainActivity.this, R.layout.flow_divider, null)); } } @Override public int getItemViewType(int position) { return list.get(position).getType(); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { ShowItem showItem = list.get(position); if ( showItem != null ) if ( showItem.getType() == 0 ) { //正文內容 TextView textView = (( MyHolder ) holder).text; textView.setText(list.get(position).des); } else { //是否顯示空格子 if ( currSelectIndex == position ) { (( MyHolderDivider ) holder).tv_divider.setVisibility(View.VISIBLE); } else { (( MyHolderDivider ) holder).tv_divider.setVisibility(View.GONE); } } } @Override public int getItemCount() { return list.size(); } class MyHolder extends RecyclerView.ViewHolder { private TextView text; public MyHolder(View itemView) { super(itemView); text = ( TextView ) itemView.findViewById(R.id.flow_text); } } class MyHolderDivider extends RecyclerView.ViewHolder { private TextView tv_divider; public MyHolderDivider(View itemView) { super(itemView); tv_divider = ( TextView ) itemView.findViewById(R.id.tv_divider); } } }
最后就是處理用戶答案和正確答案的拼接與顯示工作已經對用戶的答案進行評判的過程,像什么答案正確顯示綠色,錯誤顯示紅色,比較簡單,就不再贅述,為了減少篇幅,就不再貼出整個代碼了,感興趣的可以查看源碼,我會將源碼放到Github上,如果感覺有用,歡迎star,哈哈。
注:由于時間比較趕,所以有些地方的代碼和命名不是很規范,敬請諒解。
項目地址和結語Github地址: DragDemo
如果連接失效就直接點擊這個鏈接吧!https://github.com/MZCretin/D...
最后感謝 xiangcman/LayoutManager-FlowLayout
我是Cretin,一個可愛的小男孩
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68621.html
摘要:要快,但是我們的服務也必須萬無一失,后續我會分享百度移動端首頁的前端架構設計那么這樣的優化,是如何做到的呢,又如何兼顧穩定性,架構性,與速度呢別急,讓我們把這些優化一一道來。百度移動端首頁的很多就是這樣緩存在客戶端的。 歡迎大家收看聊一聊系列,這一套系列文章,可以幫助前端工程師們了解前端的方方面面(不僅僅是代碼):https://segmentfault.com/blog/fronte...
摘要:而這一次的項目,原本以為開發挺順利的,但是開發完了,才發現自己犯了一個低級而嚴重的錯,這樣的一個失誤,我一直耿耿于懷。但是監聽用戶退出頁面微信瀏覽器上面的那個返回或者關閉按鈕卻死活不行。也容易犯一些低級的錯誤。 1.前言 前端從事了超過兩年,修復了無數的bug,寫了無數的bug;挖了很多次坑,填了很多次坑;犯了很多次錯,彌補了很多次,學習了很多次。一般而言,對于bug、坑,都是修復完了...
摘要:一的直播答題時什么的直播答題主要發生在三個環境下端移動瀏覽器和微信端,微信端包括微信瀏覽器和微信小程序。除了要注意部署邊緣節點,轉碼和中繼也需要部署邊緣幾點,所以微信端直播與答題的同步問題會加重。 各大平臺為了給自家的直播答題爭搶流量,已經絞盡腦汁,不斷在玩法上進行創新。這場競爭從 iOS 平臺蔓延至 Android 平臺。目前大多數平臺獲取用戶的方式還是通過分享邀請碼,下載 App ...
閱讀 728·2021-08-17 10:11
閱讀 1594·2019-08-30 11:15
閱讀 1017·2019-08-26 13:54
閱讀 3502·2019-08-26 11:47
閱讀 1212·2019-08-26 10:20
閱讀 2815·2019-08-23 18:35
閱讀 1213·2019-08-23 17:52
閱讀 1296·2019-08-23 16:19