android LabelView 라벨 클 라 우 드 효과 구현
어,녹화 화면 은 이 정도 밖 에 안 돼.살아 서 봐.오늘 우 리 는 이 효 과 를 실현 해 보 겠 습 니 다.이번 에는 view 를 직접 계승 하 겠 습 니 다.뭐라고 요?이런 거 Surface View 잘 하 는 거 아니에요?왜 view 를 해 야 합 니까?사실은 다 됐 습 니 다.저 는 view 를 선택 한 이 유 는 어,저 는 Surface View 에 대해 잘 모 르 기 때 문 입 니 다.
잔말 말고 다음 부터 코드 를 올 리 겠 습 니 다.
public class LabelView extends View {
private static final int DIRECTION_LEFT = 0; //
private static final int DIRECTION_RIGHT = 1; //
private static final int DIRECITON_TOP = 2; //
private static final int DIRECTION_BOTTOM = 3; //
private boolean isStatic; // , false, xml : label:is_static="false"
private int[][] mLocations; // label x/y
private int[][] mDirections; // label x/y
private int[][] mSpeeds; // label x/y x/y
private int[][] mTextWidthAndHeight; // labeltext width/height
private String[] mLabels; // labels
private int[] mFontSizes; // label
//
private int[] mColorSchema = {0XFFFF0000, 0XFF00FF00, 0XFF0000FF, 0XFFCCCCCC, 0XFFFFFFFF};
private int mTouchSlop; // touch
private int mDownX = -1;
private int mDownY = -1;
private int mDownIndex = -1; // index
private Paint mPaint;
private Thread mThread;
private OnItemClickListener mListener; // item
public LabelView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LabelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LabelView, defStyleAttr, 0);
isStatic = ta.getBoolean(R.styleable.LabelView_is_static, false);
ta.recycle();
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
init();
}
@Override
protected void onDraw(Canvas canvas) {
if(!hasContents()) {
return;
}
for (int i = 0; i < mLabels.length; i++) {
mPaint.setTextSize(mFontSizes[i]);
if(i < mColorSchema.length) mPaint.setColor(mColorSchema[i]);
else mPaint.setColor(mColorSchema[i-mColorSchema.length]);
canvas.drawText(mLabels[i], mLocations[i][0], mLocations[i][1], mPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = (int) ev.getX();
mDownY = (int) ev.getY();
mDownIndex = getClickIndex();
break;
case MotionEvent.ACTION_UP:
int nowX = (int) ev.getX();
int nowY = (int) ev.getY();
if (nowX - mDownX < mTouchSlop && nowY - mDownY < mTouchSlop
&& mDownIndex != -1 && mListener != null) {
mListener.onItemClick(mDownIndex, mLabels[mDownIndex]);
}
mDownX = mDownY = mDownIndex = -1;
break;
}
return true;
}
/**
* label
* @return label , -1
*/
private int getClickIndex() {
Rect downRect = new Rect();
Rect locationRect = new Rect();
for(int i=0;i<mLocations.length;i++) {
downRect.set(mDownX - mTextWidthAndHeight[i][0], mDownY
- mTextWidthAndHeight[i][1], mDownX
+ mTextWidthAndHeight[i][0], mDownY
+ mTextWidthAndHeight[i][1]);
locationRect.set(mLocations[i][0], mLocations[i][1],
mLocations[i][0] + mTextWidthAndHeight[i][0],
mLocations[i][1] + mTextWidthAndHeight[i][1]);
if(locationRect.intersect(downRect)) {
return i;
}
}
return -1;
}
/**
* postInvalidate
*/
private void run() {
if(mThread != null && mThread.isAlive()) {
return;
}
mThread = new Thread(mStartRunning);
mThread.start();
}
private Runnable mStartRunning = new Runnable() {
@Override
public void run() {
for(;;) {
SystemClock.sleep(100);
for (int i = 0; i < mLabels.length; i++) {
if (mLocations[i][0] <= getPaddingLeft()) {
mDirections[i][0] = DIRECTION_RIGHT;
}
if (mLocations[i][0] >= getMeasuredWidth()
- getPaddingRight() - mTextWidthAndHeight[i][0]) {
mDirections[i][0] = DIRECTION_LEFT;
}
if(mLocations[i][1] <= getPaddingTop() + mTextWidthAndHeight[i][1]) {
mDirections[i][1] = DIRECTION_BOTTOM;
}
if (mLocations[i][1] >= getMeasuredHeight() - getPaddingBottom()) {
mDirections[i][1] = DIRECITON_TOP;
}
int xSpeed = 1;
int ySpeed = 2;
if(i < mSpeeds.length) {
xSpeed = mSpeeds[i][0];
ySpeed = mSpeeds[i][1];
}
else {
xSpeed = mSpeeds[i-mSpeeds.length][0];
ySpeed = mSpeeds[i-mSpeeds.length][1];
}
mLocations[i][0] += mDirections[i][0] == DIRECTION_RIGHT ? xSpeed : -xSpeed;
mLocations[i][1] += mDirections[i][1] == DIRECTION_BOTTOM ? ySpeed : -ySpeed;
}
postInvalidate();
}
}
};
/**
* 、 、label
*
*/
private void init() {
if(!hasContents()) {
return;
}
int minX = getPaddingLeft();
int minY = getPaddingTop();
int maxX = getMeasuredWidth() - getPaddingRight();
int maxY = getMeasuredHeight() - getPaddingBottom();
Rect textBounds = new Rect();
for (int i = 0; i < mLabels.length; i++) {
int[] location = new int[2];
location[0] = minX + (int) (Math.random() * maxX);
location[1] = minY + (int) (Math.random() * maxY);
mLocations[i] = location;
mFontSizes[i] = 15 + (int) (Math.random() * 30);
mDirections[i][0] = Math.random() > 0.5 ? DIRECTION_RIGHT : DIRECTION_LEFT;
mDirections[i][1] = Math.random() > 0.5 ? DIRECTION_BOTTOM : DIRECITON_TOP;
mPaint.setTextSize(mFontSizes[i]);
mPaint.getTextBounds(mLabels[i], 0, mLabels[i].length(), textBounds);
mTextWidthAndHeight[i][0] = textBounds.width();
mTextWidthAndHeight[i][1] = textBounds.height();
}
if(!isStatic) run();
}
/**
* label
* @return true or false
*/
private boolean hasContents() {
return mLabels != null && mLabels.length > 0;
}
/**
* labels
* @see setLabels(String[] labels)
* @param labels
*/
public void setLabels(List<String> labels) {
setLabels((String[]) labels.toArray());
}
/**
* labels
* @param labels
*/
public void setLabels(String[] labels) {
mLabels = labels;
mLocations = new int[labels.length][2];
mFontSizes = new int[labels.length];
mDirections = new int[labels.length][2];
mTextWidthAndHeight = new int[labels.length][2];
mSpeeds = new int[labels.length][2];
for(int speed[] : mSpeeds) {
speed[0] = speed[1] = 1;
}
requestLayout();
}
/**
*
* @param colorSchema
*/
public void setColorSchema(int[] colorSchema) {
mColorSchema = colorSchema;
}
/**
* item x/y
* <p>
* speeds.length > labels.length
* <p>
* speeds.length < labels.length
*
* @param speeds
*/
public void setSpeeds(int[][] speeds) {
mSpeeds = speeds;
}
/**
* item
* @param l
*/
public void setOnItemClickListener(OnItemClickListener l) {
getParent().requestDisallowInterceptTouchEvent(true);
mListener = l;
}
/**
* item
*/
public interface OnItemClickListener {
public void onItemClick(int index, String label);
}
}
올 라 와 서 상수 4 개 를 만 들 었 는데 뭐 하 는 거 야?각 아 이 템 의 방향 을 판단 해 야 합 니 다.특정한 경계 에 이 르 렀 을 때 아 이 템 은 반대 방향 으로 이동 해 야 하기 때 문 입 니 다.두 번 째 구조 방법 에서 사용자 정의 속성 을 얻 었 고 초기 화 된 Paint 도 있 습 니 다.
계속 onLayout 를 보 세 요.사실 onLayout 는 아무것도 하지 않 았 습 니 다.init 방법 만 호출 했 습 니 다.init 방법 을 보 세 요.
/**
* 、 、label
*
*/
private void init() {
if(!hasContents()) {
return;
}
int minX = getPaddingLeft();
int minY = getPaddingTop();
int maxX = getMeasuredWidth() - getPaddingRight();
int maxY = getMeasuredHeight() - getPaddingBottom();
Rect textBounds = new Rect();
for (int i = 0; i < mLabels.length; i++) {
int[] location = new int[2];
location[0] = minX + (int) (Math.random() * maxX);
location[1] = minY + (int) (Math.random() * maxY);
mLocations[i] = location;
mFontSizes[i] = 15 + (int) (Math.random() * 30);
mDirections[i][0] = Math.random() > 0.5 ? DIRECTION_RIGHT : DIRECTION_LEFT;
mDirections[i][1] = Math.random() > 0.5 ? DIRECTION_BOTTOM : DIRECITON_TOP;
mPaint.setTextSize(mFontSizes[i]);
mPaint.getTextBounds(mLabels[i], 0, mLabels[i].length(), textBounds);
mTextWidthAndHeight[i][0] = textBounds.width();
mTextWidthAndHeight[i][1] = textBounds.height();
}
if(!isStatic) run();
}
init 방법 에서 올 라 와 서 탭 이 설정 되 어 있 는 지 여 부 를 판단 하고 설정 되 어 있 지 않 으 면 바로 돌아 가 는 것 이 편리 합 니 다.10~13 줄 의 목적 은 아 이 템 이 이 view 에서 이동 하 는 상하 좌우 경 계 를 가 져 오 는 것 입 니 다.아 이 템 은 전체 view 에서 이동 해 야 하기 때문에 view 의 경 계 를 초과 해 서 는 안 됩 니 다.
17 줄,모든 탭 을 옮 겨 다 니 는 for 순환 을 시작 합 니 다.
18~20 줄 은 무 작위 로 한 위 치 를 초기 화 하 는 것 이기 때문에 우리 의 라벨 이 매번 나타 나 는 위 치 는 무 작위 이 고 규칙 적 이지 않 지만 다음 이동 은 규칙 적 이 므 로 여기저기 뛰 어 다 닐 수 는 없 잖 아 요.
이 어 22 줄 은 이 위 치 를 저장 했다.왜냐하면 우 리 는 아래 에서 이 위 치 를 계속 수정 해 야 하기 때문이다.
23 줄 은 글꼴 크기,24,25 줄 을 무 작위 로 하고 이 탭 x/y 의 초기 방향 을 무 작위 로 합 니 다.
27 줄,현재 탭 의 글꼴 크기 를 설정 합 니 다.28 줄 은 탭 의 너비 와 높이 를 가 져 오고 아래 에 2 차원 배열 에 저 장 됩 니 다.왜 2 차원 배열 입 니까?우 리 는 여러 개의 탭 이 있 습 니 다.모든 탭 은 너비 와 높이 를 저장 해 야 합 니 다.
마지막 으로,만약 우리 가 표시 하지 않 은 성명 labelview 가 정지 되 어 있다 면,run 방법 을 호출 하 십시오.
코드 를 계속 따라 가서 run 방법의 내장 을 보 세 요.
/**
* postInvalidate
*/
private void run() {
if(mThread != null && mThread.isAlive()) {
return;
}
mThread = new Thread(mStartRunning);
mThread.start();
}
5~7 줄,만약 에 스 레 드 가 열 렸 다 면 직접 return 은 여러 스 레 드 가 공존 하 는 것 을 방지 합 니 다.그러면 결 과 는 라벨 이 점점 빨 라 지 는 것 입 니 다.9,10 줄,스 레 드 를 시작 하고 mStartRunning 의 Runnable 인자 가 있 습 니 다.
그럼 이 Runnable 을 계속 보 겠 습 니 다.
private Runnable mStartRunning = new Runnable() {
@Override
public void run() {
for(;;) {
SystemClock.sleep(100);
for (int i = 0; i < mLabels.length; i++) {
if (mLocations[i][0] <= getPaddingLeft()) {
mDirections[i][0] = DIRECTION_RIGHT;
}
if (mLocations[i][0] >= getMeasuredWidth()
- getPaddingRight() - mTextWidthAndHeight[i][0]) {
mDirections[i][0] = DIRECTION_LEFT;
}
if(mLocations[i][1] <= getPaddingTop() + mTextWidthAndHeight[i][1]) {
mDirections[i][1] = DIRECTION_BOTTOM;
}
if (mLocations[i][1] >= getMeasuredHeight() - getPaddingBottom()) {
mDirections[i][1] = DIRECITON_TOP;
}
int xSpeed = 1;
int ySpeed = 2;
if(i < mSpeeds.length) {
xSpeed = mSpeeds[i][0];
ySpeed = mSpeeds[i][1];
}else {
xSpeed = mSpeeds[i-mSpeeds.length][0];
ySpeed = mSpeeds[i-mSpeeds.length][1];
}
mLocations[i][0] += mDirections[i][0] == DIRECTION_RIGHT ? xSpeed : -xSpeed;
mLocations[i][1] += mDirections[i][1] == DIRECTION_BOTTOM ? ySpeed : -ySpeed;
}
postInvalidate();
}
}
};
이 Runnable 이 야 말로 태그 클 라 우 드 가 실현 하 는 관건 입 니 다.우 리 는 이 스 레 드 에서 모든 태그 의 위 치 를 수정 하고 view 에 다시 그 리 는 것 을 알 립 니 다.그리고 run 에서 죽은 순환 을 볼 수 있 습 니 다.그래 야 우리 의 라벨 이 끊임없이 이동 할 수 있 습 니 다.그 다음 에 스 레 드 를 100 ms 쉬 게 하 는 것 입 니 다.계속 이동 할 수 는 없 잖 아 요.속도 가 너무 빨 라 도 좋 지 않 고 성능 문제 도 고려 해 야 합 니 다.
다음 일곱 번 째 줄 은 모든 탭,8~23 줄 을 옮 겨 다 니 며 현재 위치 가 특정한 경계 에 이 르 렀 는 지 판단 하고 도착 하면 방향 을 반대 방향 으로 바 꿉 니 다.예 를 들 어 지금 view 의 맨 위 에 이 르 렀 으 면 이 탭 은 아래로 이동 해 야 합 니 다.
25,26 줄,x/y 의 속 도 를 기본 으로 합 니 다.왜 기본 이 라 고 합 니까?모든 탭 의 x/y 속 도 는 우리 가 방법 을 통 해 설정 할 수 있 기 때 문 입 니 다.
다음 28~34 줄 은 하나의 판단 을 했다.대체적으로 설정 한 속도 가 현재 탭 s 에 있 는 위치 보다 많 으 면 해당 하 는 위치 속 도 를 찾 아 라.그렇지 않 으 면 앞에서 속 도 를 다시 얻 는 것 이다.
36,37 줄 은 x/y 의 방향 에 따라 현재 탭 의 좌 표를 수정 하 는 것 이다.
마지막 으로 post Invaidate()를 호출 하여 view 에 게 화면 을 새로 고침 하 라 고 알 렸 습 니 다.여 기 는 post Invaidate()를 사용 합 니 다.우 리 는 스 레 드 에서 호출 되 었 기 때문에 기억 하 십시오.
post Invaidate()후 에는 반드시 onDraw()로 가서 이 라벨 을 그 려 야 합 니 다.그러면 onDraw 를 보 겠 습 니 다.
@Override
protected void onDraw(Canvas canvas) {
if(!hasContents()) {
return;
}
for (int i = 0; i < mLabels.length; i++) {
mPaint.setTextSize(mFontSizes[i]);
if(i < mColorSchema.length) mPaint.setColor(mColorSchema[i]);
else mPaint.setColor(mColorSchema[i-mColorSchema.length]);
canvas.drawText(mLabels[i], mLocations[i][0], mLocations[i][1], mPaint);
}
}
올 라 와 서 판단 해 봤 는데 태그 가 설정 되 어 있 지 않 으 면 바로 돌아 갑 니 다.탭 이 있다 면 모든 탭 을 옮 겨 다 니 며 해당 하 는 글꼴 크기 를 설정 하 십시오.기억 하 십 니까?초기 화 할 때 모든 탭 의 글꼴 크기 를 무 작위 로 설정 한 다음 에 이 탭 의 색상 을 설정 합 니 다.if else 원리 와 설정 속도 가 같 습 니 다.가장 중요 한 것 은 바로 아래 입 니 다.canvas.draw Text()를 호출 하여 이 탭 을 화면 에 그 렸 습 니 다.mLocations 에 서 는 모든 탭 의 위 치 를 저장 하 였 습 니 다.그것 도 스 레 드 에서 이 위 치 를 계속 수정 하 는 거 야.여기까지,사실 우리 의 LabelView 는 움 직 일 수 있 지만,그 몇 개의 탭,속도,색깔 을 설정 하 는 방법 은 또 말 할 수 있다.사실 간단 해 요.한번 보 세 요.
/**
* labels
* @see setLabels(String[] labels)
* @param labels
*/
public void setLabels(List<String> labels) {
setLabels((String[]) labels.toArray());
}
/**
* labels
* @param labels
*/
public void setLabels(String[] labels) {
mLabels = labels;
mLocations = new int[labels.length][2];
mFontSizes = new int[labels.length];
mDirections = new int[labels.length][2];
mTextWidthAndHeight = new int[labels.length][2];
mSpeeds = new int[labels.length][2];
for(int speed[] : mSpeeds) {
speed[0] = speed[1] = 1;
}
requestLayout();
}
/**
*
* @param colorSchema
*/
public void setColorSchema(int[] colorSchema) {
mColorSchema = colorSchema;
}
/**
* item x/y
* <p>
* speeds.length > labels.length
* <p>
* speeds.length < labels.length
*
* @param speeds
*/
public void setSpeeds(int[][] speeds) {
mSpeeds = speeds;
}
이 몇 개의 알 이 아 픈 방법 중 유일 하 게 말 할 수 있 는 것 은 setLabels(String[]labels)이다.왜냐하면 이 방법 에서 일 도 좀 했 기 때문이다.자세히 살 펴 보면 이 방법 은 태그 s 를 설정 한 것 을 제외 하고 몇 개의 배열 을 초기 화 하 는 것 입 니 다.무엇 을 표시 하 는 지 잘 알 것 입 니 다.그리고 여기 서 우 리 는 기본 속 도 를 1 로 초기 화 했 습 니 다.처음 프 리 젠 테 이 션 을 하 러 올 라 왔 을 때 LabelView 는 아 이 템 을 클릭 할 수 있 었 는데 어떻게 했 을 까?일반적인 onClick 은 안 됩 니 다.우 리 는 클릭 한 x/y 좌 표를 모 르 기 때문에 onTouch Event 를 통 해 얻 을 수 밖 에 없습니다.
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = (int) ev.getX();
mDownY = (int) ev.getY();
mDownIndex = getClickIndex();
break;
case MotionEvent.ACTION_UP:
int nowX = (int) ev.getX();
int nowY = (int) ev.getY();
if (nowX - mDownX < mTouchSlop && nowY - mDownY < mTouchSlop
&& mDownIndex != -1 && mListener != null) {
mListener.onItemClick(mDownIndex, mLabels[mDownIndex]);
}
mDownX = mDownY = mDownIndex = -1;
break;
}
return true;
}
onTouch 에서 우 리 는 다운 과 업 이벤트 에 만 관심 을 가 졌 습 니 다.한 번 클릭 하면 다운 과 업 의 조합 이 니까 요.down 에서 우 리 는 현재 이벤트 가 발생 한 x/y 좌 표를 얻 었 고 현재 클릭 한 item 을 얻 었 습 니 다.현 재 는 getClickIndex()방법 으로 얻 었 습 니 다.이 방법 은 잠시 만 기 다 려 주 십시오.다시 한 번 up 을 살 펴 보 겠 습 니 다.up 에서 우 리 는 현재 의 x/y 와 down 에 있 을 때의 x/y 를 비교 합 니 다.만약 에 이 두 가지 거리 가 시스템 이 생각 하 는 최소 미끄럼 거리 보다 작 아야 클릭 이 유효 하 다 는 것 을 설명 할 수 있 습 니 다.만약 에 down 이후 에 긴 선 을 당 겼 다 면 up 은 효과 적 인 클릭 이 아 닐 것 입 니 다.물론 클릭 이 효과 가 있 으 면 모든 것 을 설명 할 수 없습니다.명중 라벨 만 있 습 니 다.그래서 mDownIndex 가 유효한 값 인지 판단 하고 ItemClick 을 설정 하면 되 돌려 줍 니 다.
그럼 mDownIndex 는 어떻게 얻 은 거 예요?getClickIndex()를 살 펴 보 겠 습 니 다.
/**
* label
* @return label , -1
*/
private int getClickIndex() {
Rect downRect = new Rect();
Rect locationRect = new Rect();
for(int i=0;i<mLocations.length;i++) {
downRect.set(mDownX - mTextWidthAndHeight[i][0], mDownY
- mTextWidthAndHeight[i][1], mDownX
+ mTextWidthAndHeight[i][0], mDownY
+ mTextWidthAndHeight[i][1]);
locationRect.set(mLocations[i][0], mLocations[i][1],
mLocations[i][0] + mTextWidthAndHeight[i][0],
mLocations[i][1] + mTextWidthAndHeight[i][1]);
if(locationRect.intersect(downRect)) {
return i;
}
}
return -1;
}
먼저 Rect 두 개 를 정 의 했 습 니 다.하 나 는 클릭 한 rect 이 고,다른 하 나 는 탭 의 rect 입 니 다.그리고 저 장 된 최신 탭 의 위 치 를 옮 겨 다 니 며 순환 중 에 Rect.set()방법 으로 down 의 사각형 상하 좌우 와 현재 탭 의 상하 왼쪽 오른쪽 을 각각 설정 한 다음 Rect.intersect()방법 으로 이 두 사각형 이 교차 하 는 지 여 부 를 판단 합 니 다.교차 집합 이 있 으 면 이 탭 을 클릭 했다 는 것 을 증명 합 니 다.이 탭 이 탭 s 에 있 는 위 치 를 되 돌려 줍 니 다.만약 에 되 돌아 오지 않 았 다 면-1 은 가장귀 가 어 지 러 웠 음 을 나타 냅 니 다!자,여기까지.모든 LabelView 가 다 되 었 으 니 어서 소스 코드 를 다운로드 해서 체험 해 보 세 요.물론 완벽 하 지 는 않 습 니 다.완벽 한 해결 방안 은 사용 할 때 해결 하 겠 습 니 다.헤헤,어쨌든 우 리 는 이미 생각 이 있 습 니 다.
아,맞다.아직 원본 다운로드 주 소 를 알려 주지 않 았 어여기,이곳
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Kotlin의 기초 - 2부지난 글에서는 Kotlin이 무엇인지, Kotlin의 특징, Kotlin에서 변수 및 데이터 유형을 선언하는 방법과 같은 Kotlin의 기본 개념에 대해 배웠습니다. 유형 변환은 데이터 변수의 한 유형을 다른 데이터...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.