Androidは京東のトップページの輪播の文字の効果をまねます。


京東クライアントの交互放送文字効果:

今回実現したのは後にスクロールした文字だけです。(前のものはImageViewまたはTextViewで実現すればいいです。)実現した効果を見てください。

構想を実現する

上の図はただ大雑把な考えで、実現するにはもっと多くの細部を改善する必要があります。以下は一歩ずつこの効果を実現します。
1.パッケージデータソース:図から見ると、ロードショーの文字は二つの部分に分けられています。とりあえず、それらをプレフィックスとコンテンツと呼びます。そして実際の使用過程で、ロードショーチャートをクリックすると必ずジャンプページが必要です。そして、ほとんどはWebViewです。クリックする時に必要な内容はリンクです。データソースの構造は分かります。
ADEnityクラスを作成し、いくつかの基本的な方法を改善します。コードは以下の通りです。

public class ADEnity {
 private String mFront ; //     
 private String mBack ; //     
 private String mUrl ;//     

 public ADEnity(String mFront, String mBack,String mUrl) {
  this.mFront = mFront;
  this.mBack = mBack;
  this.mUrl = mUrl;
 }

 public String getmUrl() {
  return mUrl;
 }

 public void setmUrl(String mUrl) {
  this.mUrl = mUrl;
 }

 public String getmFront() {
  return mFront;
 }

 public void setmFront(String mFront) {
  this.mFront = mFront;
 }

 public String getmBack() {
  return mBack;
 }

 public void setmBack(String mBack) {
  this.mBack = mBack;
 }
}
2.次はカスタムViewです。まず構想を整理して、構造図を見ます。

このカスタムViewを実現するすべてのパラメータは上の表に記載されています。ほとんどのパラメータは分かりやすく、個別パラメータを入れる必要があります。例えば、文字の縦座標に初期化するかどうか、文字が移動中かどうか、その後の内容は詳細に述べることができます。手で描く前に、基本的な知識を知る必要があります。文字の描き方についてですが、細かいところがたくさんあります。

本方法は、座標( x , y )に指定された文字列を描画することがよく理解されているが、x,yは、文字の左上の座標点であるべきではない。x座標はPaintの属性に応じて変換可能であり、デフォルトのxは文字の左の座標であり、Paintpaint.setTextAlign(Paint.Align.CENTER)が設定されている場合、Y;それは文字の中心位置です。baseline座標は文字のybaseline座標です。文字を描くbaselineについて:
絵で話しましょう

図中の青の線はpaint.getTextBound()であり、上の座標でも下の座標でもないことが分かります。文字を描くときは必ずテキストを真ん中に描きたいです。この時はgetTextBounds(String text, int start, int end, Rect bounds)方法を導入します。Rectペアが入ってきます。この方法を呼び出したら、このrectオブジェクトを塗りつぶします。充填された内容は、baselineに対する描画されたテキストのオフセット座標であり、このRectをbaselineの座標に加えて描画したものである。

でも、彼の値は(2,-25,76,3)だけです。baselineの位置に対して図を描いたほうが分かりやすいです。

テキストを真ん中に描画する場合、実際にbaselineを描画する座標はコンポーネントの中心であるべきです。テキストの中心である図中枠の中間座標はbaselineに対するオフセット値です。テキストを真ん中に描きます。実際にbaselineを描画する座標はグループの中心であり、テキストの中心(すなわち図中枠の中間座標)をbaselineに対するオフセット値です。

この図では、実際に文字を描く座標とコンポーネントの中心座標の関係がよく分かります。オフセット値の計算については、通常の幾何学的計算方法で、コンポーネントの中心座標+オフセット値の絶対値==Baseline座標(実際に描画した座標)となりますが、枠の座標値はすべてbaselineに対して計算されます。topは負、bottonは正の値です。このオフセット値は直接に(top+bottom)/2で表してもいいです。読めなかった学生はスケッチしてもいいです。top=-25、bottom=3で計算してみてください。結果は同じです。
上のように理解してから、文字を正しく描く方法も確定しました。
コンポーネントの高さint mHeightが取得されています。テキストの外枠Rect boundの場合
テキストを真ん中に描く

 mHeight / 2 - (bound.top + bound.bottom) / 2
//     mY       
//    
//mheight /2 = mY + (bound.top + bound.bottom) / 2 ;
テキストを最高点にスクロール

mY == 0 - bound.bottom
//     mY     ,            
//    
//mY + bound.bottom = 0 ;
テキストは一番低いところにスクロールして、ちょうどコンポーネントから出ます。

 mY = mHeight - indexBound.top;
//     mY     ,            
//    
//mY + bound.top = mHeight ;
どのように正確に文字と境界を描くかを知ると、次は文字を描くステップになります。
まずデータを初期化して、デフォルトを設定します。

//      
private void init() {
 mDuration = 500;
 mInterval = 1000;
 mIndex = 0;
 mPaintFront = new Paint();
 mPaintFront.setAntiAlias(true);
 mPaintFront.setDither(true);
 mPaintFront.setTextSize(30);


 mPaintBack = new Paint();
 mPaintBack.setAntiAlias(true);
 mPaintBack.setDither(true);
 mPaintBack.setTextSize(30);

}

前の説明では、最初に入るときは、文字はコンポーネントの底にあるはずですが、この値はコンポーネントの高さと現在の表示文字を取得する必要がある場合に判断されますので、ODraw内に置いてこの値を初期化するべきです。前の初期化属性が必要です。mY==0で初期化されていない場合はmYに値を付与します。
次はonDraw内の処理です。
現在のデータを取得

//       
ADEnity model = mTexts.get(mIndex);
String font = model.getmFront();
String back = model.getmBack();
//        
Rect indexBound = new Rect();
mPaintFront.getTextBounds(font, 0, font.length(), indexBound);

//       
Rect contentBound = new Rect();
mPaintBack.getTextBounds(back, 0, back.length(), contentBound);

mYを初期化する

if (mY == 0 && hasInit == false) {
 mY = getMeasuredHeight() - indexBound.top;
 hasInit = true;
}
境界状況に対する処理
を選択します。

/      
if (mY == 0 - indexBound.bottom) {
 Log.i(TAG, "onDraw: " + getMeasuredHeight());
 mY = getMeasuredHeight() - indexBound.top;//    
 mIndex++;//      
}
//     
if (mY == getMeasuredHeight() / 2 - (indexBound.top + indexBound.bottom) / 2) {
 isMove = false;//    
 Timer timer = new Timer();
 timer.schedule(new TimerTask() {
  @Override
  public void run() {
   postInvalidate();//    
   isMove = true;//     true
  }
 }, mInterval);//            
}
            
mY -= 1;//         ,        
//      
if (mIndex == mTexts.size()) {
 mIndex = 0;
}
//           ,     
//         ,            ,        1  
if (isMove) {
 postInvalidateDelayed(mDuration / getMeasuredHeight());
}
            ,          
public interface onClickLitener {
 public void onClick(String mUrl);
}

private onClickLitener onClickLitener;

public void setOnClickLitener(TextViewAd.onClickLitener onClickLitener) {
 this.onClickLitener = onClickLitener;
}
//  onTouchEvent  ,     true,                   
@Override
public boolean onTouchEvent(MotionEvent event) {
 int action = event.getAction();

 switch (action) {
  case MotionEvent.ACTION_DOWN:
   //    ,            
   if (onClickLitener != null) {
    onClickLitener.onClick(mTexts.get(mIndex).getmUrl());
   }

   break;
 }
 return true;
}
             
//     
public void setmTexts(List mTexts) {
 this.mTexts = mTexts;
}

//           
public void setmInterval(int mInterval) {
 this.mInterval = mInterval;
}

//             
public void setmDuration(int mDuration) {
 this.mDuration = mDuration;
}

//         
public void setFrontColor(int mFrontColor) {
 mPaintFront.setColor(mFrontColor);
}

//         
public void setBackColor(int mBackColor) {
 mPaintBack.setColor(mBackColor);
}
                attrs.xml                   ,       ,      copy  View   xml   copy    ,  as     ,                .(      ).
      ADTextView   ,    
package com.qiyuan.jindongshangcheng.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

import com.qiyuan.jindongshangcheng.enity.ADEnity;

import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Created by huanghaojie on 2016/9/30.
 */

public class TextViewAd extends TextView {

 private int mDuration; //             
 private int mInterval; //               
 private List<ADEnity> mTexts; //        
 private int mY = 0; //   Y  
 private int mIndex = 0; //       
 private Paint mPaintBack; //       
 private Paint mPaintFront; //       
 private boolean isMove = true; //      
 private String TAG = "ADTextView";
 private boolean hasInit = false;//                

 public interface onClickLitener {
  public void onClick(String mUrl);
 }

 private onClickLitener onClickLitener;

 public void setOnClickLitener(TextViewAd.onClickLitener onClickLitener) {
  this.onClickLitener = onClickLitener;
 }

 public TextViewAd(Context context) {
  this(context, null);
 }

 public TextViewAd(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }
 //  onTouchEvent  ,     true,                   
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  int action = event.getAction();

  switch (action) {
   case MotionEvent.ACTION_DOWN:
    //    ,            
    if (onClickLitener != null) {
     onClickLitener.onClick(mTexts.get(mIndex).getmUrl());
    }

    break;
  }
  return true;
 }

 //     
 public void setmTexts(List mTexts) {
  this.mTexts = mTexts;
 }

 //           
 public void setmInterval(int mInterval) {
  this.mInterval = mInterval;
 }

 //             
 public void setmDuration(int mDuration) {
  this.mDuration = mDuration;
 }

 //         
 public void setFrontColor(int mFrontColor) {
  mPaintFront.setColor(mFrontColor);
 }

 //         
 public void setBackColor(int mBackColor) {
  mPaintBack.setColor(mBackColor);
 }

 //      
 private void init() {
  mDuration = 500;
  mInterval = 1000;
  mIndex = 0;
  mPaintFront = new Paint();
  mPaintFront.setAntiAlias(true);
  mPaintFront.setDither(true);
  mPaintFront.setTextSize(30);


  mPaintBack = new Paint();
  mPaintBack.setAntiAlias(true);
  mPaintBack.setDither(true);
  mPaintBack.setTextSize(30);

 }

 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  super.onSizeChanged(w, h, oldw, oldh);
  Log.i(TAG, "onSizeChanged: " + h);
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if (mTexts != null) {
   Log.i(TAG, "onDraw: " + mY);
   //       
   ADEnity model = mTexts.get(mIndex);
   String font = model.getmFront();
   String back = model.getmBack();
   //        
   Rect indexBound = new Rect();
   mPaintFront.getTextBounds(font, 0, font.length(), indexBound);

   //       
   Rect contentBound = new Rect();
   mPaintBack.getTextBounds(back, 0, back.length(), contentBound);
   //                      ,                              ,
   //       onDraw        ,               ,   mY==0          mY  .
   if (mY == 0 && hasInit == false) {
    mY = getMeasuredHeight() - indexBound.top;
    hasInit = true;
   }
   //      
   if (mY == 0 - indexBound.bottom) {
    Log.i(TAG, "onDraw: " + getMeasuredHeight());
    mY = getMeasuredHeight() - indexBound.top;//    
    mIndex++;//      
   }
   canvas.drawText(back, 0, back.length(), (indexBound.right - indexBound.left) + 20, mY, mPaintBack);
   canvas.drawText(font, 0, font.length(), 10, mY, mPaintFront);
   //     
   if (mY == getMeasuredHeight() / 2 - (indexBound.top + indexBound.bottom) / 2) {
    isMove = false;//    
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
     @Override
     public void run() {
      postInvalidate();//    
      isMove = true;//     true
     }
    }, mInterval);//            
   }
   //            
   mY -= 1;//         ,        
   //      
   if (mIndex == mTexts.size()) {
    mIndex = 0;
   }
   //           ,     
   //         ,            ,        1  
   if (isMove) {
    postInvalidateDelayed(mDuration / getMeasuredHeight());
   }
  }

 }
}

     ?
1,  xml            
<?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="vertical">
 <com.qiyuan.jindongshangcheng.view.TextViewAd
  android:id="@+id/textad"
  android:layout_width="match_parent"
  android:layout_height="wrap_content" />

</LinearLayout>

2. MainActivity   
/**
 * Created by huanghaojie on 2016/9/30.
 */

public class MainActivity extends Activity {
 private TextViewAd textViewAd;
 private List<ADEnity> mList;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main4);
  textViewAd = (TextViewAd) findViewById(R.id.textad);
  mList = new ArrayList<>();
  for (int i = 0; i < 10; i++) {
   ADEnity adEnity = new ADEnity("  " + i, "  " + i, "http://www.baidu.com"+i);
   mList.add(adEnity);
  }
  textViewAd.setmTexts(mList);
  textViewAd.setFrontColor(Color.RED);
  textViewAd.setBackColor(Color.BLUE);
  textViewAd.setmDuration(1000);
  textViewAd.setmInterval(1000);
  textViewAd.setOnClickLitener(new TextViewAd.onClickLitener() {
   @Override
   public void onClick(String mUrl) {
    Toast.makeText(MainActivity.this,"   "+mUrl,Toast.LENGTH_LONG).show();
   }
  });
 }
}
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。