Android 手機影音 開發過程記錄(二)

前一篇已經將SplashActivity編寫好了,這篇主要梳理一下主頁面MainActivity。包括:

  1. 實現ViewPager上方的頭佈局Tab的高亮和縮放動畫;
  2. 實現指示線的隨手指移動而移動的效果

基類的編寫

  • 一般項目開發中,會涉及到很多的Activity和Fragment的使用,而且我們在這些activity或者fragment中操作的方法大致一樣:初始化view,初始化data,初始化listener,以及實現一些控件的onClick方法。

  • 所以爲了方便,我們將這些方法向上抽取,放到一個BaseActivity以及一個BaseFragment當中去。當需要使用的時候,直接繼承我們已經寫好的這些基類,這樣,不僅少了不少重複代碼,而且可讀性極大增強,邏輯更加清晰。

public abstract class BaseActivity extends FragmentActivity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initViews();
        initData();
        initListeners();
    }

    protected abstract void initViews();
    protected abstract void initData();
    protected abstract void initListeners();
    /**可以處理共同點擊事件的按鈕(比如像一些項目的TitleBar都有一個返回按鈕)*/
    protected abstract void processClick(View view);

    @Override
    public void onClick(View view) {
        processClick(view);
    }
}

說明:這裏基類繼承的是FragmentActivity而不是Activity,主要是考慮到主頁面的viewpager嵌套了兩個fragment,它的的適配器是繼承FragmentPagerAdapter,這是v4包下的,所以主頁面需要通過繼承FragmentActivity來getSupportFragmentManager得到這個fragmentManager。

這裏順道把BaseFragment的編寫也貼上,後面要用到。跟BaseActivity差不多。

public abstract class BaseFragment extends Fragment implements View.OnClickListener {

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = initView(inflater, container, savedInstanceState);
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initData();
        initListener();
    }

    protected abstract View initView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);

    protected abstract void initData();

    protected abstract void initListener();

    protected abstract void processClick(View view);

    @Override
    public void onClick(View v) {
        processClick(v);
    }

    protected void enterActivity(Class<?> targetClass) {
        startActivity(new Intent(getActivity(), targetClass));
    }

    protected void enterActivity(Class<?> targetClass, Bundle bundle) {
        Intent intent = new Intent(getActivity(), targetClass);
        intent.putExtras(bundle);
        startActivity(intent);
    }

說明:這裏多了enterActivity()的兩個重載的方法,是根據該項目需求抽取的。因爲該項目中,fragment中加入的是一個listview控件,點擊其中的一個item會跳轉到一個activity中去,有時帶數據,有時不帶,爲了代碼整潔,所以都抽取到BaseFragment中了。

主頁面佈局以及標題欄Tab高亮&縮放效果

  • 前提:既然是自己定義標題欄,就不能忘了把系統自帶的標題欄給去掉。
    在style.xml文件中AppTheme的樣式下加入下面代碼:
<item name="android:windowNoTitle">true</item>

1. 主頁面佈局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@mipmap/base_bg"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:background="@mipmap/base_titlebar_bg">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/tv_tab_video"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="視頻"
                android:textColor="@color/indicate_line"
                android:textSize="18sp" />

            <TextView
                android:id="@+id/tv_tab_audio"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="音樂"
                android:textColor="@color/gray_white"
                android:textSize="18sp" />
        </LinearLayout>

        <View
            android:id="@+id/indicate_line"
            android:layout_width="0dp"
            android:layout_height="3dp"
            android:layout_alignParentBottom="true"
            android:background="@color/indicate_line" />

    </RelativeLayout>


    <android.support.v4.view.ViewPager
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </android.support.v4.view.ViewPager>


</LinearLayout>

2. 標題欄高亮及縮放:

  • 這裏縮放的動畫效果是用的屬性動畫,API_12及以上纔有,如果想兼容低版本,可以用 nineoldandroids-2.4.0.jar來實現。
    /**
     * 高亮顯示並且縮放標籤標題
     * @param position viewpager的currentItem的position
     */
    private void highligthAndScaleTabTitle(int position) {
        tvVideo.setTextColor(position == 0 ? getResources().getColor(R.color.indicate_line)
                : getResources().getColor(R.color.gray_white));
        tvAudio.setTextColor(position == 1 ? getResources().getColor(R.color.indicate_line)
                : getResources().getColor(R.color.gray_white));

        tvVideo.animate().
                scaleX(position == 0 ? 1.2f : 1.0f).
                scaleY(position == 0 ? 1.2f : 1.0f).
                setDuration(200);
        tvAudio.animate().
                scaleX(position == 0 ? 1.0f : 1.2f).
                scaleY(position == 0 ? 1.0f : 1.2f).
                setDuration(200);
    }

標題欄指示線的隨手指移動而移動效果

思考:

  1. 指示線的寬是多少了呢?該項目中是屏幕寬的一半,但能寫死嗎?萬一後來需求改了,標題欄要求添加一個tab呢?很顯然,這個指示線不能寫死,得根據標籤的數量動態去設置,兩個標籤,寬就是屏幕的1/2,三個標籤,寬就是屏幕的1/3。fragment的數量就是標籤的數量,因爲每一個標籤對應一個fragment。
  2. 如何讓指示線隨手指的滑動而滑動呢?是不是應該這樣:viewpager滑動一個頁面的距離(即屏幕寬),指示線就應該移動它本身的寬度。在viewpager的onPageScrolled(int position, float positionOffset, int positionOffsetPixels)方法中,參數position是當前fragment的位置,參數positionOffset就是手指滑動距離佔屏幕總寬的比率,參數positionOffsetPixels就是滑動的像素值。
  3. 有了滑動比率,指示線的寬,就好辦了,拿 lineWidth * positionOffset不就是指示線應該滑動的距離嗎。原理就是這麼簡單,但要注意,指示線在移動的時候,要把原始的位置加上,不然,它會一直在屏幕左邊滑動。lineWidth * position + lineWidth * positionOffset。
  4. 偏移亮計算出來了,這裏還是用屬性動畫來實現隨手指滑動。通過setDuration(0)來達到效果。

貼張圖再次加深對上面文字的理解。
這裏寫圖片描述

計算指示線寬度的代碼:

    /**
     * 計算指示器的寬度
     */
    private void calculateIndicateLineWidth() {
        Point point = new Point();
        this.getWindowManager().getDefaultDisplay().getSize(point);
        int screenWidth = point.x;
        lineWidth = screenWidth / fragments.size();
        LogUtils.i("lineWidth = " + lineWidth);
        indicateLine.getLayoutParams().width = lineWidth;
        indicateLine.requestLayout();
    }

指示線隨手指滑動的代碼:(在監聽viewpager的onPageScrolled()的方法裏設置)

            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//                LogUtils.i("position = " + position + ",positionOffset = " + positionOffset
//                        + ",positionOffsetPixels = " + positionOffsetPixels);
                float delatX = lineWidth * (position + positionOffset);
                LogUtils.i("delatX = " + delatX);
                indicateLine.animate().translationX(delatX).setDuration(0);
            }

主頁面完整代碼

說明:該項目用了xUtils框架中的註解功能,省去大量的 findViewById()操作。

public class MainActivity extends BaseActivity {

    @ViewInject(R.id.vp)
    private ViewPager vp;
    @ViewInject(R.id.tv_tab_video)
    private TextView tvVideo;
    @ViewInject(R.id.tv_tab_audio)
    private TextView tvAudio;
    @ViewInject(R.id.indicate_line)
    private View indicateLine;

    private List<BaseFragment> fragments;
    private int lineWidth;//指示線的寬度

    protected void initViews() {
        setContentView(R.layout.activity_main);
        ViewUtils.inject(this);
    }

    protected void initData() {
        fragments = new ArrayList<>();
        fragments.add(new VideoListFragment());
        fragments.add(new AudioListFragment());
        calculateIndicateLineWidth();
        MainUiAdapter adapter = new MainUiAdapter(getSupportFragmentManager(), fragments);
        vp.setAdapter(adapter);
        highligthAndScaleTabTitle(0);
    }

    protected void initListeners() {
        tvVideo.setOnClickListener(this);
        tvAudio.setOnClickListener(this);
        vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//                LogUtils.i("position = " + position + ",positionOffset = " + positionOffset
//                        + ",positionOffsetPixels = " + positionOffsetPixels);
                float delatX = lineWidth * (position + positionOffset);
                LogUtils.i("delatX = " + delatX);
                indicateLine.animate().translationX(delatX).setDuration(0);
            }

            @Override
            public void onPageSelected(int position) {
                highligthAndScaleTabTitle(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }

    @Override
    protected void processClick(View view) {
        switch (view.getId()) {
            case R.id.tv_tab_video:
                vp.setCurrentItem(0);
                break;
            case R.id.tv_tab_audio:
                vp.setCurrentItem(1);
                break;
        }
    }

    /**
     * 高亮顯示並且縮放標籤標題
     * @param position viewpager的currentItem的position
     */
    private void highligthAndScaleTabTitle(int position) {
        tvVideo.setTextColor(position == 0 ? getResources().getColor(R.color.indicate_line)
                : getResources().getColor(R.color.gray_white));
        tvAudio.setTextColor(position == 1 ? getResources().getColor(R.color.indicate_line)
                : getResources().getColor(R.color.gray_white));

        tvVideo.animate().
                scaleX(position == 0 ? 1.2f : 1.0f).
                scaleY(position == 0 ? 1.2f : 1.0f).
                setDuration(200);
        tvAudio.animate().
                scaleX(position == 0 ? 1.0f : 1.2f).
                scaleY(position == 0 ? 1.0f : 1.2f).
                setDuration(200);
    }

    /**
     * 計算指示器的寬度
     */
    private void calculateIndicateLineWidth() {
        Point point = new Point();
        this.getWindowManager().getDefaultDisplay().getSize(point);
        int screenWidth = point.x;
        lineWidth = screenWidth / fragments.size();
        LogUtils.i("lineWidth = " + lineWidth);
        indicateLine.getLayoutParams().width = lineWidth;
        indicateLine.requestLayout();
    }

    private long exitTime = 0;

    @Override
    public void onBackPressed() {
        if(System.currentTimeMillis() - exitTime > 2000){
            ToastUtil.showShort(this, "再按一次退出手機影音");
            exitTime =System.currentTimeMillis();
        }else{
            finish();
        }
    }
}

好了,先整理到這裏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章