1. 程式人生 > >Android拖動,縮放,自定義內容,控制元件製作(可拖動縮放RelativeLayout定製)

Android拖動,縮放,自定義內容,控制元件製作(可拖動縮放RelativeLayout定製)

先上效果圖:


一.     製作此控制元件的起源

專案需要一個可以拖動的控制元件,在網上可以找到很多例子,有圖片拖動控制元件,有textview拖動控制元件。但是專案中需要控制元件同時可以動態通過手指調整尺寸,並且控制元件的內容不固定,需要自定義內容,即可以新增任意內容到拖動控制元件內。因此,編寫此控制元件。

二.     根據需求做技術分析

1.     可拖動+調整尺寸:view的(scrollTo、scrollBy),設定LayoutParams,覆蓋layout方法

2.     自定義內容:需要自定義的控制元件記憶體放其他控制元件,則需要自定義控制元件繼承ViewGroup(LinearLayout、ReletiveLayout)

三.     Android自定義控制元件所需基礎知識

a 位置座標:

螢幕左上角是座標原點(0,0),原點向右延伸是x正軸方向,原點向下延伸是y軸正方向

自定義控制元件的座標位置是相對於父控制元件的:getTop()、getBottom(),getLeft(),getRight(),這幾個函式用於獲取自定義view在父佈局座標系的位置。

b  觸控感知

繼承onTouchEvent,獲取使用者對自定義控制元件的觸控事件(down,move,up)

根據觸控的位置event.getX(),event.getY(),以及其他位置,判斷要執行的的操作。包括根據位移移動,根據位移縮放。根據位移判斷是否到達邊界。

c 自定義控制元件父類選擇

由於需求中控制元件裡面的內容不定,即可以動態新增任意型別的Android控制元件到自定義的控制元件裡面,因此這個自定義控制元件不能通過繼承View實現,需要繼承ViewGroup來實現,為了使用一些佈局功能,最後專案選定繼承ViewGroup的子類RelativeLayout,以實現動態新增任意多個任意型別的View控制元件。

c  移動控制元件

上文提到移動控制元件的三個方法:view的(scrollTo、scrollBy), 設定LayoutParams,覆蓋layout方法。

Layout:我測試過layout移動控制元件,是可以達到移動控制元件的目的,但是破壞了控制元件尺寸計算路徑,(本來是onmeasure之後,onlayout,由於我是自定義viewGroup現在是直接改動了layout,導致控制元件尺寸錯亂,最後造成ViewGroup裡面的內容顯示不完整),layout介入了view/ViewGroup的地層繪製過程,造成混亂。

LayoutParams:這個引數一般是用於Android的xml佈局檔案裡面,比如:layoutout_height=”” , layout_width=”” ,layout_marginLeft=””,layout_marginTop=””

假定在一個LinearLayout裡面放一個imageView,通過修改ImageView的這幾個引數就可以讓ImageView在LinearLayout裡面自由的移動位置:如圖:

對自定義控制元件的位置設定轉化為:(marginLeft,marginTop,width,height)

其中,marginLeft和marginTop負責確定控制元件的位置,width和height確定控制元件的大小,(可以看著圖按自己的方式理解),總之是,通過這幾引數的修改,可以使得控制元件在LinearLayout或RelativeLayout佈局內自由的移動並且變換大小。

四.     可移動控制元件程式碼編寫

原理都寫清楚了,開始編寫程式碼,定義一個類繼承RelativeLayout,覆蓋,onTouchEvent,然後編寫邏輯程式碼:核心程式碼如下(都貼上上看著心累):

public class MoveLayout extends RelativeLayout {

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                oriLeft = getLeft();
                oriRight = getRight();
                oriTop = getTop();
                oriBottom = getBottom();

                lastY = (int) event.getRawY();
                lastX = (int) event.getRawX();
                dragDirection = getDirection((int) event.getX(), (int) event.getY());
                break;
            case MotionEvent.ACTION_UP:
                             break;
            case MotionEvent.ACTION_MOVE:
                int tempRawX = (int)event.getRawX();
                int tempRawY = (int)event.getRawY();

                int dx = tempRawX - lastX;
                int dy = tempRawY - lastY;
                lastX = tempRawX;
                lastY = tempRawY;

                switch (dragDirection) {
                    case LEFT:
                        left( dx);
                        break;
                    case RIGHT:
                        right( dx);
                        break;
                    case BOTTOM:
                        bottom(dy);
                        break;
                    case TOP:
                        top( dy);
                        break;
                    case CENTER:
                        center( dx, dy);
                        break;
                }

                //把新的位置 oriLeft, oriTop, oriRight, oriBottom設定到控制元件,實現位置移動和大小變化。
                RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(oriRight - oriLeft, oriBottom - oriTop);
                lp.setMargins(oriLeft,oriTop,0,0);
                setLayoutParams(lp);
                           break;
        }
        return super.onTouchEvent(event);
    }
/**
 * 觸控點為中心->>移動
 */
private void center(int dx, int dy) {
    int left = getLeft() + dx;
    int top = getTop() + dy;
    int right = getRight() + dx;
    int bottom = getBottom() + dy;

    if (left < 0) {
        left = 0;
        right = left + getWidth();
    }
    if (right > screenWidth ) {
        right = screenWidth ;
        left = right - getWidth();
    }
    if (top < 0) {
        top = 0;
        bottom = top + getHeight();
    }
    if (bottom > screenHeight ) {
        bottom = screenHeight ;
        top = bottom - getHeight();
    }

    oriLeft = left;
    oriTop = top;
    oriRight = right;
    oriBottom = bottom;
}
}

五.     控制元件管理程式碼

以上實現了一個拖動和改變大小的控制元件,其實就是實現了一個定製的RelativeLayout,定製的RelativeLayout可以被拖動,和改變大小。因此可以在MoveLayout內新增任意view實現自己的顯示效果。

現在為了能在一個佈局上動態的增加很多個可移動的控制元件,並且對這些控制元件做管理功能,(動態增加、動態刪除)

為了實現這個功能,又自定義了一個RelativeLayout來放置多個MoveLayout,動態增加,刪除。新自定義的RelativeLayout叫做:DragView:簡略程式碼如下:

public class DragView extends RelativeLayout implements MoveLayout.DeleteMoveLayout{

    public DragView(Context context) {
        super(context);
        init(context, this);
    }

    private void init(Context c, DragView thisp) {
        mContext = c;
        mMoveLayoutList = new ArrayList<>();
    }
/*
*通過ondraw獲取DragView的實際大小,然後告訴所有被管理的MoveLayout
*/
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
      //  Log.e(TAG, "onDraw: height=" + getHeight());
        mSelfViewWidth = getWidth();
        mSelfViewHeight = getHeight();

        if (mMoveLayoutList != null) {
            int count = mMoveLayoutList.size();
            for (int a = 0; a < count; a ++) {
                mMoveLayoutList.get(a).setViewWidthHeight(mSelfViewWidth, mSelfViewHeight);
                mMoveLayoutList.get(a).setDeleteWidthHeight(DELETE_AREA_WIDTH, DELETE_AREA_HEIGHT);
            }
        }
    }

//新增新的MoveLayout,並在裡面放置自己的控制元件selfView,同時制定位置和尺寸
    public void addDragView(View selfView, int left, int top ,int right, int bottom, boolean isFixedSize, boolean whitebg) {
        MoveLayout moveLayout = new MoveLayout(mContext);
        moveLayout.setClickable(true);
        moveLayout.setMinHeight(mMinHeight);
        moveLayout.setMinWidth(mMinWidth);
        int tempWidth = right - left;
        int tempHeight = bottom - top;
        if (tempWidth < mMinWidth) tempWidth = mMinWidth;
        if (tempHeight < mMinHeight) tempHeight = mMinHeight;

        //set postion
        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(tempWidth, tempHeight);
        lp.setMargins(left,top,0,0);
        moveLayout.setLayoutParams(lp);

        //add sub view (has click indicator)
        LayoutInflater inflater = LayoutInflater.from(mContext);
        View dragSubView = inflater.inflate(R.layout.drag_sub_view, null);
        LinearLayout addYourViewHere = (LinearLayout) dragSubView.findViewById(R.id.add_your_view_here);
        LinearLayout.LayoutParams lv = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        addYourViewHere.addView(selfView, lv);

        moveLayout.addView(dragSubView);
        //set fixed size
        moveLayout.setFixedSize(isFixedSize);

        addView(moveLayout);
        mMoveLayoutList.add(moveLayout);
    }
//新增新的MoveLayout,並在裡面放置自己的控制元件selfView,同時制定位置和尺寸
    public void addDragView(int resId, int left, int top ,int right, int bottom, boolean isFixedSize, boolean whitebg) {
        LayoutInflater inflater2 = LayoutInflater.from(mContext);
        View selfView = inflater2.inflate(resId, null);
        addDragView(selfView, left, top , right, bottom, isFixedSize, whitebg);
    }

    @Override
    public void onDeleteMoveLayout(int identity) {
        int count = mMoveLayoutList.size();
        for (int a = 0; a < count; a ++) {
            if (mMoveLayoutList.get(a).getIdentity() == identity) {
                //delete
                removeView(mMoveLayoutList.get(a));
            }
        }
    }

}

六.     在自己專案里加入方法推薦

        本控制元件實現上非常簡單,當然也穩定,並且顯示控制元件定製化要求高,因此建議直接複製類到自己的工程裡,並進行個性化的修改,因此這個控制元件沒有做成庫檔案。整個控制元件的實現和呼叫工程。

自定義控制元件包括:MoveLayout.java,DragView,drag_sub_view.xml,corners_bg.xml,corners_bg2.xml,spot_corners_bg.xml

七.     下載