1. 程式人生 > >漫畫播放器一吐槽功能

漫畫播放器一吐槽功能

前言

近來有些莫名的浮躁,浮躁的心態總讓我靜不下心來學習。新的一篇的文章到現在才跟大家見面,非常欣慰一直關注我的小夥伴,有你們的陪伴,一路不孤單。

Google IO大會,谷歌宣佈,將Kotlin語言作為安卓開發的一級程式語言。這裡推薦兩篇乾貨給大家,連結地址如下:

漫畫吐槽

先來一張圖,看看最終的實現效果:

GIF.gif

由繁到簡,拆分吐槽功能為以下幾個自定義控制元件:

  • 右下角的懸浮按鈕

float

  • 底部帶輸入框的 Dialog

dialog

  • 吐槽控制元件(根據手指移動,並且可以在邊緣擠壓)

gossip

  • 吐槽展示控制元件

layout

接下來,我們逐一揪一揪各個控制元件的具體實現方式。

懸浮按鈕(FloatButtonView)

實現功能一欄:

  • 展開與收起動畫

  • 關閉,開啟狀態

  • 實時更新吐槽數量

  • 發表按鈕介面實現

實現流程如下:

(1)、建構函式

    public FloatButtonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //新增檢視
        addLayoutView();
        //初始化動畫
        initData();
        //新增監聽
        setListener();
    }

(2)、addLayoutView() 新增檢視

    private void addLayoutView() {
        View view = LayoutInflater.from(getContext()).inflate(R.layout.view_float_btn, null, false);
        //設定 MATCH_PARENT 不然 layout_marginBottom 無效
        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH
_PARENT, ViewGroup.LayoutParams .MATCH_PARENT)); mCloseImg = (ImageView) view.findViewById(R.id.img_close); mCloseTv = (TextView) view.findViewById(R.id.tv_close); mGossipNumTv = (TextView) view.findViewById(R.id.tv_num); mCloseLayout = (RadiusLinearLayout) view.findViewById(R.id.layout_close); mPublishLayout = (RadiusLinearLayout) view.findViewById(R.id.layout_publish); mGossipLayout = (RadiusLinearLayout) view.findViewById(R.id.layout_gossip); addView(view); }

view_float_btn.xml 佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <com.github.gossipdemo.radius.RadiusLinearLayout
        android:id="@+id/layout_close"
        android:layout_width="38dp"
        android:layout_height="38dp"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="12dp"
        android:layout_above="@+id/layout_publish"
        android:layout_marginRight="15dp"
        android:gravity="center"
        android:orientation="vertical"
        app:rv_backgroundColor="#AF000000"
        app:rv_radiusHalfHeightEnable="true">

        <ImageView
            android:id="@+id/img_close"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:src="@mipmap/ic_comp_close"/>

        <TextView
            android:id="@+id/tv_close"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="關閉"
            android:textColor="#FFF"
            android:textSize="8sp"/>


    </com.github.gossipdemo.radius.RadiusLinearLayout>


    <com.github.gossipdemo.radius.RadiusLinearLayout
        android:layout_width="38dp"
        android:id="@+id/layout_publish"
        android:layout_height="38dp"
        android:layout_above="@+id/layout_gossip"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="12dp"
        android:layout_marginRight="15dp"
        android:gravity="center"
        android:orientation="vertical"
        app:rv_backgroundColor="#AF000000"
        app:rv_radiusHalfHeightEnable="true">


        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:src="@mipmap/ic_comp_publish"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="發表"
            android:textColor="#FFF"
            android:textSize="8sp"/>


    </com.github.gossipdemo.radius.RadiusLinearLayout>

    <com.github.gossipdemo.radius.RadiusLinearLayout
        android:id="@+id/layout_gossip"
        android:layout_width="44dp"
        android:layout_height="44dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="12dp"
        android:layout_marginRight="12dp"
        android:gravity="center"
        android:orientation="vertical"
        app:rv_backgroundColor="#AF000000"
        app:rv_radiusHalfHeightEnable="true">

        <TextView
            android:id="@+id/tv_num"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="0"
            android:textColor="#FFF"
            android:textSize="10sp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="1dp"
            android:text="吐槽"
            android:textColor="#FFF"
            android:textSize="12sp"/>

    </com.github.gossipdemo.radius.RadiusLinearLayout>

</RelativeLayout>

注意:新增的子檢視為 RelativeLayout 相對佈局,設定 android:layout_marginBottom 會失效,針對該問題的解決方法是:給該子檢視設定佈局引數的高度為填充父窗體。

view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams
        .MATCH_PARENT));

(3)、initData() 初始化展開,縮放動畫

    private void initData() {
        mOpenAnimator = openAnimator();
        mCloseAnimator = closeAnimator();
    }

展開與縮放動畫:

    /**
     * 展開動畫
     *
     * @return
     */
    private ValueAnimator openAnimator() {
        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
        animator.setDuration(500);
        animator.setInterpolator(new OvershootInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                setAnimatorParams(animation);
            }
        });
        return animator;
    }

    public ValueAnimator closeAnimator() {
        ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0f);
        animator.setDuration(200);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                setAnimatorParams(animation);
            }
        });
        return animator;
    }

    /**
     * 設定動畫引數
     *
     * @param animation
     */
    private void setAnimatorParams(ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();

        mCloseLayout.setY(mGossipLayout.getY() - mCloseMove * value);
        mPublishLayout.setY(mGossipLayout.getY() - mPublishMove * value);

        mCloseLayout.setAlpha(value);
        mPublishLayout.setAlpha(value);
    }

實現過程比較簡單,看看效果圖:

float.gif

底部帶輸入框的 Dialog

看到這裡你可能會有疑問了,為啥要使用 Dialog 呢,直接在漫畫播放器底部新增輸入框不就 ok 了嗎?

最開始我也是這麼幹的,可是結局太悲催了。整整浪費了我一整天的時間。神坑啊 …

神坑之一:

軟鍵盤的彈起覆蓋輸入框?

這個還不簡單,設定 android:windowSoftInputMode 軟鍵盤的輸入模式,然後你會發現並沒卵用。

之後就抓狂,噴血,嘗試了各種百度,google,各大論壇搜尋,各種方式嘗試 . . .,我還不相信了小樣,我還解決不了你 . . .

現實太殘酷,我失敗了。

神坑之二:

軟鍵盤的彈出和收起會影響漫畫播放器的檢視滾動?

內心已經跑過一萬匹草泥馬 …

最後我通過檢視【網易動漫】的 頂部 Activity,發現人家是採用 Dialog 的方式來實現的。這使我內心平復了一丟丟,原來網易也跟我踩過相同的坑啊。

top

強力推薦以下兩款工具來檢視和分析第三方 app 檢視層級:

2、SDK 自帶的 uiautomatorviewer.bat

uia

下面我貼出 Dialog 的實現程式碼:

public class AppDialog extends Dialog {

    private Context context;
    private View contentView;
    private EditText compEditText;
    private OnPublishListener listener;
    private String gossipText = "";

    public AppDialog(@NonNull Context context, String text) {
        super(context);
        this.context = context;
        this.gossipText = text;
        addContentView();

        setCanceledOnTouchOutside(true);
    }

    public AppDialog(@NonNull Context context) {
        this(context, "");
    }

    private void addContentView() {
        //佈局的高度引數不要設定為 match_parent  不然 setCanceledOnTouchOutside 方法無效
        View view = LayoutInflater.from(context).inflate(R.layout.view_app_dialog, null);
        setContentView(view);
        //設定dialog大小
        this.contentView = view;
        this.compEditText = (EditText) view.findViewById(R.id.player_edit_view);

        compEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (listener != null) {
                    listener.publish(v);
                }
                return false;
            }
        });

        if (gossipText != null) {
            compEditText.setText(gossipText);
            compEditText.setSelection(compEditText.getText().length());
        }

        Window dialogWindow = getWindow();
        WindowManager manager = ((Activity) context).getWindowManager();
        WindowManager.LayoutParams params = dialogWindow.getAttributes(); // 獲取對話方塊當前的引數值
        dialogWindow.setGravity(Gravity.BOTTOM);//對齊方式為底部對齊
        Display d = manager.getDefaultDisplay(); // 獲取螢幕寬、高度
        params.width = (int) (d.getWidth() * 1.0f); // 寬度設定為螢幕寬度,根據實際情況調整

        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);//必須設定 不然輸入框會被覆蓋
        getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));// android:windowBackground
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);// android:backgroundDimEnabled預設是true的

        dialogWindow.setAttributes(params);
    }
 }

接下來是吐槽控制元件的實現,篇幅有點長,原始碼在文章的末尾給出。

吐槽控制元件(GossipView)

先來看看吐槽控制元件的效果圖,如下:

gossip.gif

大概實現了以下幾個效果:

  • 跟隨手指而移動

  • 邊緣擠壓

  • 多指擠壓

(1)、跟隨手指而移動

重寫 onTouchEvent 方法,返回 true 消費事件。

手指按下的同時記錄觸控點的 X ,Y 座標:

case MotionEvent.ACTION_DOWN:
   simTouchX = event.getX();
   simTouchY = event.getY();

手機移動時刻記錄手指的偏移量:

 case MotionEvent.ACTION_MOVE:
    float dx = event.getX() - simTouchX;
    float dy = event.getY() - simTouchY;
    //contentLayout 表示隨著手指移動的控制元件
   contentLayout.setX(contentLayout.getX() + dx);
   contentLayout.setY(contentLayout.getY() + dy);

    simTouchX = event.getX();
    simTouchY = event.getY();

注意手指移動的範圍【螢幕範圍內】,超過螢幕的顯示需要邊緣處理。

(2)邊緣擠壓

邊緣擠壓主要分為:

  • 上邊緣擠壓(寬度不斷增大,高度不斷減少)

  • 下邊緣擠壓(寬度不斷增大,高度不斷減少)

  • 左邊緣擠壓(寬度不斷減少,高度不斷增大)

  • 右邊緣擠壓(寬度不斷減少,高度不斷增大)

上邊緣擠壓,分析 Y 座標的偏移量(dy 正數),動態改變控制元件的佈局引數(寬度=控制元件寬度+dy;高度=自適應父窗體),實現擠壓效果。

首先獲取到控制元件的佈局引數

FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) itemView.getLayoutParams();

動態改變控制元件寬度

 lp.width = itemView.getWidth()+ dy; //dy表示兩點Y座標偏移量

計算控制元件寬度的最大值和最小值,如果控制元件的寬度小於等於最小值則最終的寬度為最小寬度,反之則為最大寬度:

    //注意寬度的區間  取最大值和最小值
    Rect rect = new Rect();
    //itemView為文字控制元件
    itemView.getPaint().getTextBounds(content, 0, itemView.getText().toString().length(), rect);
    //最小值和最大值
    int minWidth = rect.height() + itemView.getPaddingTop() + itemView.getPaddingBottom();
    int maxWidth = rect.width() + itemView.getPaddingLeft() + itemView.getPaddingRight();
    //越界處理
    if (lp.width <= minWidth) {
            lp.width = minWidth;
        } else if (lp.width >= maxWidth) {
            lp.width = maxWidth;
        }

最後重置控制元件的寬度:

   itemView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, FrameLayout.LayoutParams.WRAP_CONTENT));

下,左,右邊緣擠壓與上邊緣擠壓類似。

(3)、多指擠壓

第二根手指按下的同時計算與第一根手指之間的距離

case MotionEvent.ACTION_POINTER_DOWN:
    //第二根手指X座標
    float multiTouchX = event.getX(1);
    //第二根手指Y座標
    float multiTouchY = event.getY(1);
    //兩根手指的x距離
    float dx = multiTouchX - simTouchX;
    //兩根手指的y距離
    float dy = multiTouchY - simTouchY;
    //兩根手指的距離
    fingersLength = (float) Math.sqrt(dx * dx + dy * dy);

然後計算手指移動的偏移量:

    float moveDx= event.getX() - event.getX(1);
    float moveDy= event.getY() - event.getY(1);
    float moveLength= (float) Math.sqrt(moveDx* moveDx+ moveDy * moveDy);
    //手指的偏移量
    float fingersDistance= moveLength- fingersLength;

最後根據偏移量呼叫擠壓方法。

吐槽展示控制元件(動態新增)

效果一欄:

gossip.png

實現的程式碼如下:

    View itemLayout = LayoutInflater.from(mGossipLayout.getContext()).inflate(R.layout
            .view_gossip_item, null);
    final RadiusTextView rtv = (RadiusTextView) itemLayout.findViewById(R.id.rvt_name);
    rtv.setLayoutParams(new LinearLayout.LayoutParams((int) width, ViewGroup.LayoutParams
            .WRAP_CONTENT));
    rtv.setText(text);
    rtv.setX(x);
    rtv.setY(y);
    mGossipLayout.addView(itemLayout);

吐槽控制元件細節處理的地方比較多,具體請參考 Demo

如果您有不懂的地方請給我留言。

技術交流歡迎您的加入

qq