仿1號店垂直滾動廣告條實現

效果圖展示,圖片有點卡,耐心看會,原程序是很流暢的

實現步驟:

  • 聲明變量
  • 初始化畫筆、文本大小和座標
  • onMeasure()適配wrap_content的寬高
  • onDraw()畫出根據座標畫出兩段Text
  • 監聽點擊事件
  • 在Activity中實現點擊事件

實現原理(座標變換原理):整個過程都是基於座標Y的增加和交換進行處理的,Y值都會一直增加到endY,然後進行交換邏輯


步驟一:聲明變量

由於1號店是兩句話的滾動,所以我們也是使用兩句話來實現的

private Paint mPaint;
private float x, startY, endY, firstY, nextStartY, secondY;

//整個View的寬高是以第一個爲標準的,所以第二句話長度必須小於第一句話
private String[] text = {"今日特賣:毛衣3.3折>>>", "公告:全場半價>>>"};
private float textWidth, textHeight;

//滾動速度
private float speech = 0;
private static final int CHANGE_SPEECH = 0x01;
//是否已經在滾動
private boolean isScroll = false;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

步驟二:初始化畫筆、文本大小和座標

以第一句話爲標準來做控件的寬高標準

//初始化畫筆
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setTextSize(30);
//測量文字的寬高,以第一句話爲標準
Rect rect = new Rect();
mPaint.getTextBounds(text[0], 0, text[0].length(), rect);
textWidth = rect.width();
textHeight = rect.height();
//文字開始的x,y座標
//由於文字是以基準線爲基線的,文字底部會突出一點,所以向上收5px
x = getX() + getPaddingLeft();
startY = getTop() + textHeight + getPaddingTop() - 5;
//文字結束的x,y座標
endY = startY + textHeight + getPaddingBottom();
//下一個文字滾動開始的y座標
//由於文字是以基準線爲基線的,文字底部會突出一點,所以向上收5px
nextStartY = getTop() - 5;
//記錄開始的座標
firstY = startY;
secondY = nextStartY;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

步驟三:onMeasure()適配wrap_content的寬高

如果學習過自定義View的話,下面的代碼應該很熟悉,就是適配warp_content的模板代碼:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = measureWidth(widthMeasureSpec);
    int height = measureHeight(heightMeasureSpec);
    setMeasuredDimension(width, height);
}

private int measureHeight(int heightMeasureSpec) {
    int result = 0;
    int size = MeasureSpec.getSize(heightMeasureSpec);
    int mode = MeasureSpec.getMode(heightMeasureSpec);
    if (mode == MeasureSpec.EXACTLY) {
        result = size;
    } else {
        result = (int) (getPaddingTop() + getPaddingBottom() + textHeight);
        if (mode == MeasureSpec.AT_MOST) {
            result = Math.min(result, size);
        }
    }
    return result;
}

private int measureWidth(int widthMeasureSpec) {
    int result = 0;
    int size = MeasureSpec.getSize(widthMeasureSpec);
    int mode = MeasureSpec.getMode(widthMeasureSpec);
    if (mode == MeasureSpec.EXACTLY) {
        result = size;
    } else {
        result = (int) (getPaddingLeft() + getPaddingRight() + textWidth);
        if (mode == MeasureSpec.AT_MOST) {
            result = Math.min(result, size);
        }
    }
    return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

步驟四:onDraw()畫出根據座標畫出兩段Text(已修復:Text停下來時閃一下的bug)

  • 通過Handler來改變速度
  • 通過isScroll鎖,來控制Handler只改變一次
  • 通過invalidate一直重繪兩句話的文字
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //啓動滾動
    if (!isScroll) {
        mHandler.sendEmptyMessageDelayed(CHANGE_SPEECH, 2000);
        isScroll = true;
    }

    canvas.drawText(text[0], x, startY, mPaint);
    canvas.drawText(text[1], x, nextStartY, mPaint);
    startY += speech;
    nextStartY += speech;

    //超出View的控件時
    if (startY > endY || nextStartY > endY) {
        if (startY > endY) {
            //第一次滾動過後交換值
            startY = secondY;
            nextStartY = firstY;
        } else if (nextStartY > endY) {
            //第二次滾動過後交換值
            startY = firstY;
            nextStartY = secondY;
        }
        speech = 0;
        isScroll = false;
    }
    invalidate();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case CHANGE_SPEECH:
                speech = 1f;
                break;
        }
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

步驟五:監聽點擊事件(已修復:點擊事件錯亂的問題)

在自定義View重寫dispatchTouchEvent處理點擊事件,這個也是模板代碼:

public onTouchListener listener;

public interface onTouchListener {
    void touchListener(String s);
}

public void setListener(onTouchListener listener) {
    this.listener = listener;
}

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            //點擊事件
            if (listener != null) {
                if (startY >= firstY && nextStartY < firstY) {
                    listener.touchListener(text[0]);
                } else if (nextStartY >= firstY && startY < firstY) {
                    listener.touchListener(text[1]);
                }
            }
            break;
    }
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

步驟六:在Activity中實現點擊事件

public class VerTextViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ver_text_view);

        VerTextView tv_ver = (VerTextView) findViewById(R.id.tv_ver);
        tv_ver.setListener(new VerTextView.onTouchListener() {
            @Override
            public void touchListener(String s) {
                Toast.makeText(VerTextViewActivity.this, s, Toast.LENGTH_LONG).show();
            }
        });
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

佈局文件

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

    <ImageView
        android:layout_width="120dp"
        android:layout_height="30dp"
        android:background="@drawable/vertextview" />

    <com.handsome.app3.Custom.VerTextView.VerTextView
        android:id="@+id/tv_ver"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#ffffff"
        android:padding="8dp" />
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

整個類的源碼:

/**
 * =====作者=====
 * 許英俊
 * =====時間=====
 * 2016/10/11.
 */
public class VerTextView extends View {

    private Paint mPaint;
    private float x, startY, endY, firstY, nextStartY, secondY;

    //整個View的寬高是以第一個爲標準的,所以第二句話長度必須小於第一句話
    private String[] text = {"今日特賣:毛衣3.3折>>>", "公告:全場半價>>>"};
    private float textWidth, textHeight;

    //滾動速度
    private float speech = 0;
    private static final int CHANGE_SPEECH = 0x01;
    //是否已經在滾動
    private boolean isScroll = false;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case CHANGE_SPEECH:
                    speech = 1f;
                    break;
            }
        }
    };

    public VerTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //初始化畫筆
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setTextSize(30);
        //測量文字的寬高,以第一句話爲標準
        Rect rect = new Rect();
        mPaint.getTextBounds(text[0], 0, text[0].length(), rect);
        textWidth = rect.width();
        textHeight = rect.height();
        //文字開始的x,y座標
        //由於文字是以基準線爲基線的,文字底部會突出一點,所以向上收5px
        x = getX() + getPaddingLeft();
        startY = getTop() + textHeight + getPaddingTop() - 5;
        //文字結束的x,y座標
        endY = startY + textHeight + getPaddingBottom();
        //下一個文字滾動開始的y座標
        //由於文字是以基準線爲基線的,文字底部會突出一點,所以向上收5px
        nextStartY = getTop() - 5;
        //記錄開始的座標
        firstY = startY;
        secondY = nextStartY;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = measureWidth(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    private int measureHeight(int heightMeasureSpec) {
        int result = 0;
        int size = MeasureSpec.getSize(heightMeasureSpec);
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = (int) (getPaddingTop() + getPaddingBottom() + textHeight);
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }

    private int measureWidth(int widthMeasureSpec) {
        int result = 0;
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = (int) (getPaddingLeft() + getPaddingRight() + textWidth);
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //啓動滾動
        if (!isScroll) {
            mHandler.sendEmptyMessageDelayed(CHANGE_SPEECH, 2000);
            isScroll = true;
        }

        canvas.drawText(text[0], x, startY, mPaint);
        canvas.drawText(text[1], x, nextStartY, mPaint);
        startY += speech;
        nextStartY += speech;

        //超出View的控件時
        if (startY > endY || nextStartY > endY) {
            if (startY > endY) {
                //第一次滾動過後交換值
                startY = secondY;
                nextStartY = firstY;
            } else if (nextStartY > endY) {
                //第二次滾動過後交換值
                startY = firstY;
                nextStartY = secondY;
            }
            speech = 0;
            isScroll = false;
        }
        invalidate();
    }

    public onTouchListener listener;

    public interface onTouchListener {
        void touchListener(String s);
    }

    public void setListener(onTouchListener listener) {
        this.listener = listener;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                //點擊事件
                if (listener != null) {
                    if (startY >= firstY && nextStartY < firstY) {
                        listener.touchListener(text[0]);
                    } else if (nextStartY >= firstY && startY < firstY) {
                        listener.touchListener(text[1]);
                    }
                }
                break;
        }
        return true;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章