Android高級-自定義控件

分類:

1:自繪控件:動畫,顯示加載,顯示圖表,沒有對外界交互

    

2:組合控件:recyclviewId的Item

     

3:繼承控件:對系統控件進行修改

     

4:事件類控件:對事件衝突的處理

     

5:容器類控件:流式佈局,百分比佈局這些容器類控件,可以對子控件進行重新擺放

     

我們用demo分析自定義控件;

如圖所示:

 

實現思路:上面的箭頭理解爲下圖的 “小車”

 只放重要的代碼

     private float[] pos =new float[2];
     private  float[] tan =new float[2];
     private float mFloat =0;    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,mPaint);//先畫一條橫線
        canvas.drawLine(getWidth()/2,0,getWidth()/2,getHeight(),mPaint);//再畫一條豎線
        //將圓心設置到中心
        canvas.translate(getWidth()/2,getHeight()/2);
        //再畫一個圓
          mPath.addCircle(0,0,100,Path.Direction.CW);
          canvas.drawPath(mPath,mPaint);//不通過canvans直接畫圓
          mFloat += 0.01;
        if (mFloat >= 1){  //0-1的進度)
            mFloat = 0;
        }
         //然後讓圖片顯示到圓上
          //1 最開始顯示到某個點上 這個點通過PathMeasure的方法來確定  比如在0.1這個點開始
         PathMeasure pathMeasure =new PathMeasure(mPath,false);
         //從哪個點開始=pos[] 表示的是表示當前點在畫布上的位置  tan[]表示的是(當前點pos與x軸切點的點的座標)
         pathMeasure.getPosTan(pathMeasure.getLength()*mFloat,pos,tan);
         //計算小車的旋轉的角度
         double degree = Math.atan2(tan[1],tan[0]) * 180.0 /Math.PI;
         matrix.reset();
         //將圖片根據每次不同的角度 而進行旋轉
         matrix.postRotate((float)degree,mBitmap.getWidth() /2,mBitmap.getHeight()/2);
        //將圖片的繪製點中心與當前點重合,通過上面我們已經獲取到了當前點 所以我們將圖片移動到這個位置就好了啊
          matrix.postTranslate(pos[0]-mBitmap.getWidth()/2,pos[1]-mBitmap.getHeight()/2);
          canvas.drawBitmap(mBitmap,matrix,mPaint);
          invalidate();


    }

Math.atan2() 方法可返回從 x 軸到點 (x,y) 之間的角度。

2:通過手寫RecyclerView學習組合控件

RecyclerView的用法

RecyclerView的架構在生活中的體現

傳送帶機制:

RecyclerView:

 

1:RecyclerView  VS 傳送帶  用到了哪些設計模式呢?

2:RecyclerView中的

第一屏加載

加載第一屏幕的時候,先把加載需求交給回收池,看回收池裏面有沒有這個item,如果沒有,將需求轉交給適配器的onCrateViewHodler方法返回一個佈局給這個view,然後將這個view添加到Recycview的容器中

第二屏幕

當用戶的手指滑動,第一個item被滑出屏幕之外,回收池會將這個item回收,存放在他裏面的緩存集合,

回收的同時,底部需要被item填充,他第一步回到回收池中去找,如果要被填充的佈局和回收池中的類型是一樣的,

那麼就可以進行復用,他會去之前的緩存集合中將這個item取出來,由於這個item和底下的數據類型不一致,回收池會將這個item交給適配器去刷新數據。如果沒有,就繼續第一屏的繪製過程

 

我們將移除過程叫做 移除策略

我們將底部的填充叫做填充策略

 

2:那麼回收池應該怎麼設計呢?

前景:回收池本身就是一種集合,既能夠存也能夠取-------存和取  是回收池策略必須實現的

 

3:手寫RecyclerView的代碼如何設計

代碼(需要重寫自定義哪些方法呢)

onLayout:因爲是容器,對子控件進行擺放

OnMeasure:對自身和自控件進行測量

onIntercptTouchEvent:RecyclerView只對滑動事件進行消費,點擊事件是不需消費的,需要交給子item去消費

onTouchEvent:當消費了滑動是事件後,會將子控件進行重新擺放

 

 

Recycler 回收池使用的是Stack數組:

 關於Stack  :http://baijiahao.baidu.com/s?id=1640447389353256524&wfr=spider&for=pc

import android.view.View;

import java.util.Collection;
import java.util.Stack;

public class Recycler {

    //如何查找最快  這次是要用棧
    private Stack<View>[] views ;
    public Recycler(int typeNumber) {
        views = new Stack[typeNumber];
        for (int i=0;i<typeNumber;i++) {

            views[i] = new Stack<View>();
        }
    }
    public void put(View view, int type){
        views[type].push(view);
    }
    public View get(int type) {
        try {
            return views[type].pop();
        } catch (Exception e) {
            return null;
        }

    }


}

srcollyBy:是對整個擺放的實現邏輯

RecyclerView成員變量聲明:

這次只實現簡單功能

上代碼Demo;

public class RecyclerView extends ViewGroup {
    private Adapter adapter;
    //當前顯示的View
    private List<View> viewList;
    //當前滑動的y值
    private int currentY;
    //行數
    private int rowCount;
    //view的第一行  是佔內容的幾行
    private int firstRow;
    //y偏移量
    private int scrollY;
    //初始化  第一屏最慢
    private boolean needRelayout;
    private int width;

    private int height;
    private int[] heights;//item  高度
    Recycler recycler;
    //最小滑動距離
    private int touchSlop;
    public Adapter getAdapter() {
        return adapter;
    }

    public void setAdapter(Adapter adapter) {
        this.adapter = adapter;
        if (adapter != null) {
            recycler = new Recycler(adapter.getViewTypeCount());
            scrollY = 0;
            firstRow = 0;
            needRelayout = true;
            requestLayout();//1  onMeasure   2  onLayout
        }
    }

    public RecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        ViewConfiguration configuration = ViewConfiguration.get(context);
        this.touchSlop=configuration.getScaledTouchSlop();
        this.viewList = new ArrayList<>();
        this.needRelayout = true;
    }

//初始化
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (needRelayout || changed) {
            needRelayout = false;

            viewList.clear();
            removeAllViews();
            if (adapter != null) {
                //               擺放
                width = r - l;
                height = b - t;
                int left, top = 0, right, bottom;
                for (int i = 0; i < rowCount&&top<height; i++) {
                    right = width;
                    bottom = top + heights[i];
//                    生成一個View
                   View view= makeAndStep(i, 0, top, width, bottom);
                    viewList.add(view);
                    top = bottom;//循環擺放
                }

            }

        }
    }
    private View makeAndStep(int row, int left, int top, int right, int bottom) {
        View view = obtainView(row, right - left, bottom - top);
        view.layout(left, top, right, bottom);
        return view;
    }
    private View obtainView(int row, int width, int height) {
//        key type
       int itemType= adapter.getItemViewType(row);
//       取不到
        View reclyView = recycler.get(itemType);
        View view = null;
        if (reclyView == null) {
            view = adapter.onCreateViewHodler(row, reclyView, this  );
            if (view == null) {
                throw new RuntimeException("onCreateViewHodler  必須填充佈局");
            }
        }else {
            view = adapter.onBinderViewHodler(row, reclyView, this);
        }
        view.setTag(R.id.tag_type_view, itemType);
        view.measure(MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY)
                ,MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY));
        addView(view,0 );
        return view;
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int h = 0;
        if (adapter != null) {
            this.rowCount = adapter.getCount();
            heights = new int[rowCount];
            for (int i = 0; i < heights.length; i++) {
                heights[i] = adapter.getHeight(i);
            }
        }
//        數據的高度
        int tmpH  = sumArray(heights, 0, heights.length);
        h= Math.min(heightSize, tmpH);
        setMeasuredDimension(widthSize, h);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
//    firstIndex  firstIndex+count
    private int sumArray(int array[], int firstIndex, int count) {
        int sum = 0;
        count += firstIndex;
        for (int i = firstIndex; i < count; i++) {
            sum += array[i];
        }
        return sum;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                currentY = (int) event.getRawY();
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int y2 = Math.abs(currentY - (int) event.getRawY());
                if (y2 > touchSlop) {
                    intercept = true;
                }
            }
        }
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE: {
//                移動的距離   y方向
                int y2 = (int) event.getRawY();
//         //            上滑正  下滑負
                int diffY = currentY - y2;
//                畫布移動  並不影響子控件的位置
                scrollBy(0, diffY);
            }
        }
        return super.onTouchEvent(event);
    }
 private int scrollBounds(int scrollY) {
//        上滑
        if (scrollY > 0) {
 scrollY = Math.min(scrollY,sumArray(heights,firstRow,heights.length-firstRow)-height);
 }else {
//            極限值  會取零  非極限值的情況下   socrlly
            scrollY = Math.max(scrollY, -sumArray(heights, 0, firstRow));

 }
 return scrollY;
//        下滑

    }
    @Override
    public void scrollBy(int x, int y) {
//        scrollY表示 第一個可見Item的左上頂點 距離屏幕的左上頂點的距離
        scrollY += y;
        scrollY = scrollBounds(scrollY);
//        scrolly
        if (scrollY > 0) {
//              上滑正  下滑負  邊界值
            while (scrollY > heights[firstRow]) {
//      1 上滑移除  2 上劃加載  3下滑移除  4 下滑加載
                removeView(viewList.remove(0));
                scrollY -= heights[firstRow];
                firstRow++;
            }
            while (getFillHeight() < height) {
                int addLast = firstRow + viewList.size();
               View view= obtainView(addLast, width, heights[addLast]);
                viewList.add(viewList.size(), view);
            }


        } else if (scrollY < 0) {
//            4 下滑加載
            while (scrollY < 0) {
                int firstAddRow = firstRow - 1;
                View view = obtainView(firstAddRow, width, heights[firstAddRow]);
                viewList.add(0,view);
                firstRow--;
                scrollY += heights[firstRow+1];
            }
//             3下滑移除
            while (sumArray(heights, firstRow, viewList.size()) - scrollY - heights[firstRow + viewList.size() - 1] >= height) {
                removeView(viewList.remove(viewList.size() - 1));
            }

        }else {
        }
        repositionViews();
    }
    private void repositionViews() {
        int left, top, right, bottom, i;
        top =  - scrollY;
        i = firstRow;
        for (View view : viewList) {
            bottom = top + heights[i++];
            view.layout(0, top, width, bottom);
            top = bottom;
        }
    }
    private  int getFillHeight() {
//        數據的高度 -scrollY
        return sumArray(heights, firstRow, viewList.size()) - scrollY;
    }
    private int getFilledHeight() {
//        數據高度-scrolly
        return sumArray(heights, firstRow, viewList.size()) - scrollY;
    }
    @Override
    public void removeView(View view) {
        super.removeView(view);
        int key= (int) view.getTag(R.id.tag_type_view);
        recycler.put(view, key);
    }

    interface Adapter {
        View onCreateViewHodler(int position, View convertView, ViewGroup parent);
        View onBinderViewHodler(int position, View convertView, ViewGroup parent);
        //Item的類型
        int getItemViewType(int row);
        //Item的類型數量
        int getViewTypeCount();
        int getCount();
        public int getHeight(int index);
    }
}

 

 

 

 

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