1. 程式人生 > >自定義ViewGroup和FrameLayout實現輪播圖(包括底部小圓點)

自定義ViewGroup和FrameLayout實現輪播圖(包括底部小圓點)

廣告輪播圖在現在的APP首頁比較常見,主要的實現方式有兩種,一種是通過ViewPager,一種是通過自定義ViewGroup。前者的實現方式比較簡便,本篇文章講的是第二種方法,有人說用ViewPager不是更方便嗎,的確,但是我們通過自己定義ViewGroup,可以更深入瞭解ViewGroup內部的原理。用別人造的輪子確實方便,但有的時候拆開輪子看看,我們也許會學到更多。

效果圖
這裡寫圖片描述

主要的思路如下:
首先,輪播圖可以理解為n張圖片橫向相連,並通過一個單張圖片大小的的相框,一次移動一張圖片的距離,我們可以通過一個ViewGroup容器來存放這n張圖片,然後用Scroller類配合TimeTask和Handler來實現圖片的滑動,至於小圓點,也就是一個橫向存放圓點圖片的LinearLayout佈局,可以再繼承一個FrameLayout類,把存放圖片的ViewGroup和存放圓點的LinearLayout都放進去,並根據圖片滑動的位置來設定圓點的切換。

這就需要對ViewGroup的測量過程(onMeasure),佈局過程 (onLayout)和繪製過程(onDraw)以及onTouch點選事件的處理有所瞭解。

直接看程式碼
ViewGroup類:

package com.imagebanner;


import android.content.Context;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import
android.view.View; import android.view.ViewGroup; import android.widget.Scroller; import java.util.Timer; import java.util.TimerTask; /** * 該類是實現圖片輪播核心類 */ public class ImageBannerViewGroup extends ViewGroup { //子檢視個數 private int children ; //子檢視寬度和高度 private int childWidth ; private
int childHeight ; private int x ; private int index = 0 ; private Scroller scroller ; //圖片點選事件的監聽器 private ImageBannerListner listner ; //底部圓點切換的監聽器 private ImageBannerViewGroupListener bannerViewGroupListener ; public ImageBannerViewGroupListener getBannerViewGroupListener() { return bannerViewGroupListener; } public void setBannerViewGroupListener(ImageBannerViewGroupListener bannerViewGroupListener) { this.bannerViewGroupListener = bannerViewGroupListener; } //判斷是點選事件還是移動事件的標識 private boolean isClick ; public ImageBannerListner getListner() { return listner; } public void setListner(ImageBannerListner listner) { this.listner = listner; } public interface ImageBannerListner{ void clickImageIndex( int pos ); } //判斷是否自動輪播的標識 private boolean isAuto = true ; //自動輪播 private Timer timer = new Timer(); private TimerTask task ; private Handler autoHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch ( msg.what ){ case 0: //最後一張的時候返回第一張 if( ++index >= children ){ index = 0 ; } scrollTo( childWidth * index , 0 ); //圖片切換完畢後通知FrameLayout切換底部圓點 bannerViewGroupListener.selectImage(index); break; } } }; private void startAuto(){ isAuto = true ; } private void stopAuto(){ isAuto = false ; } private void init(){ scroller = new Scroller(getContext()); task = new TimerTask() { @Override public void run() { if( isAuto ){ autoHandler.sendEmptyMessage(0); } } }; timer.schedule(task , 100 , 3000 ); } @Override public void computeScroll() { super.computeScroll(); if( scroller.computeScrollOffset()){ scrollTo(index * childWidth, 0); invalidate(); } } public ImageBannerViewGroup(Context context) { super(context); init(); } public ImageBannerViewGroup(Context context, AttributeSet attrs) { super(context, attrs); init(); } public ImageBannerViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //求出子檢視的個數 children = getChildCount(); if( children == 0 ){ setMeasuredDimension(0,0); }else{ //測量子檢視的高度和寬度 measureChildren(widthMeasureSpec,heightMeasureSpec); //根據子檢視的寬度和高度 , 求出該ViewGroup的寬度和高度 View view = getChildAt(0); childHeight = view.getMeasuredHeight() ; childWidth = view.getMeasuredWidth() ; //子檢視的總寬度 int width = childWidth * children ; setMeasuredDimension( width , childHeight ); } } /** * * @param change 佈局位置發生改變時為true * @param l 相對於父View的Left位置 * @param t 相對於父View的Top位置 * @param r 相對於父View的Right位置 * @param b 相對於父View的Bottom位置 */ @Override protected void onLayout(boolean change, int l, int t, int r, int b) { if( change ){ int leftMargin = 0 ; for( int i = 0 ; i < children ; i ++ ){ View view = getChildAt(i); view.layout(leftMargin, 0 , leftMargin + childWidth , childHeight ); leftMargin += childWidth ; } } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } /** * 該方法返回true , ViewGroup會處理此次攔截事件 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true ; } @Override public boolean onTouchEvent(MotionEvent event) { switch ( event.getAction() ){ case MotionEvent.ACTION_DOWN: isClick = true ; stopAuto(); if( !scroller.isFinished() ){ scroller.abortAnimation(); } x = (int) event.getX(); break ; case MotionEvent.ACTION_MOVE: int moveX = (int) event.getX(); int distance = moveX - x ; scrollBy( -distance , 0 ); x = moveX ; isClick = false ; break ; case MotionEvent.ACTION_UP: int scrollX = getScrollX() ; index = ( scrollX + childWidth/2 ) / childWidth ; if( index < 0 ){ index = 0 ; }else if( index > children - 1 ){ index = children - 1 ; } if( isClick ){ //如果是點選事件 listner.clickImageIndex(index); } else{ int dx = index * children - scrollX ; scroller.startScroll(scrollX , 0 , dx , 0 ); postInvalidate(); bannerViewGroupListener.selectImage(index); } startAuto(); /* scrollTo( index * childWidth , 0 );*/ break ; default: break ; } //返回true的目的是告訴該ViewGroup的父View已經處理該事件 return true ; } // public interface ImageBannerViewGroupListener{ void selectImage( int index ) ; } }

FrameLayout類

package com.imagebanner;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;

import java.util.List;

/**
 * Created by Administrator on 2017/7/9.
 */

public class ImageBannerFrameLayout extends FrameLayout implements ImageBannerViewGroup.ImageBannerViewGroupListener , ImageBannerViewGroup.ImageBannerListner{

    private ImageBannerViewGroup imageBannerViewGroup ;

    private LinearLayout linearLayout ;

    //自定義輪播圖的監聽器
    public FrameLayoutListener listener ;

    public FrameLayoutListener getListener() {
        return listener;
    }

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

    public ImageBannerFrameLayout(Context context) {
        super(context);
        initViewGroup();
        initDotLinearLayout();
    }

    public ImageBannerFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initViewGroup();
        initDotLinearLayout();
    }

    public ImageBannerFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initViewGroup();
        initDotLinearLayout();
    }

    //初始化圖片輪播功能的核心類
    private void initViewGroup(){
        imageBannerViewGroup = new ImageBannerViewGroup(getContext());
        //設定佈局屬性
        FrameLayout.LayoutParams fp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
        imageBannerViewGroup.setLayoutParams(fp);
        //為ViewGroup設定底部圓點切換的監聽器
        imageBannerViewGroup.setBannerViewGroupListener(this);
        //為ViewGroup設定圖片點選事件的監聽器
        imageBannerViewGroup.setListner(this);
        addView(imageBannerViewGroup);
    }

    //初始化底部圓點佈局
    private void initDotLinearLayout(){

        linearLayout = new LinearLayout(getContext());
        //設定佈局屬性
        FrameLayout.LayoutParams fp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 40);
        linearLayout.setLayoutParams(fp);
        linearLayout.setOrientation(LinearLayout.HORIZONTAL);
        linearLayout.setGravity(Gravity.CENTER);

        linearLayout.setBackgroundColor(Color.GRAY);

        addView(linearLayout);

        FrameLayout.LayoutParams layoutParams = (LayoutParams) linearLayout.getLayoutParams();
        layoutParams.gravity = Gravity.BOTTOM ;

        linearLayout.setLayoutParams(layoutParams);

        if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
            linearLayout.setAlpha( 0.5f );
        }else{
            linearLayout.getBackground().setAlpha(100);
        }

    }

    //公共方法,向FrameLayout中新增圖片
    public void addBitmap( List<Bitmap> list ){

        for( int i = 0 ; i < list.size() ; i ++ ){

            Bitmap bitmap = list.get(i);

            addBitmapToViewGroup(bitmap);

            addDots();
        }
    }

    //向ViewGroup中新增圖片
    private void addBitmapToViewGroup(Bitmap bitmap){
        ImageView iv = new ImageView(getContext());
        iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
        iv.setLayoutParams( new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        iv.setImageBitmap(bitmap);
        imageBannerViewGroup.addView(iv);
    }

    //向底部linearlayout中新增圓點
    private void addDots(){
        ImageView iv = new ImageView(getContext());
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        lp.setMargins(15,5,15,5);
        iv.setLayoutParams(lp);
        iv.setImageResource(R.drawable.dot_normal);
        linearLayout.addView(iv);
    }

    //實現該介面,完成底部圓點的切換
    @Override
    public void selectImage(int index) {
        int count = linearLayout.getChildCount();
        for( int i = 0 ; i < count ; i ++ ){
            ImageView iv = (ImageView) linearLayout.getChildAt(i);
            if( i == index ){
                iv.setImageResource(R.drawable.dot_select);
            }else {
                iv.setImageResource(R.drawable.dot_normal);
            }
        }
    }


    @Override
    public void clickImageIndex(int pos) {
        listener.clickImageIndex(pos);
    }

    //定義FrameLayout的監聽器介面
    public interface FrameLayoutListener{

        void clickImageIndex( int pos ) ;
    }
}

MainActivity程式碼

package com.imagebanner;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements ImageBannerFrameLayout.FrameLayoutListener{

    private ImageBannerFrameLayout mGroup ;

    private int[] ids = new int[]{
            R.drawable.banner1,
            R.drawable.banner2,
            R.drawable.banner3
    };

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

        //計算出當前手機的寬度
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        int width = dm.widthPixels;

        mGroup = (ImageBannerFrameLayout) findViewById(R.id.image_banner);
        mGroup.setListener(this);

        List<Bitmap> list = new ArrayList<>();
        for( int i = 0 ; i < ids.length ; i ++ ){
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(),ids[i]);
            list.add(bitmap);
        }

        mGroup.addBitmap(list);

    }

    //此處填寫點選事件相關的業務程式碼
    @Override
    public void clickImageIndex(int pos) {
          Toast.makeText(this,"點選了第" + pos +  "張圖片" , Toast.LENGTH_SHORT).show();
    }
}

圓點佈局檔案dot_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@android:color/white"></solid>

    <size android:height="10dp"
        android:width="10dp"></size>

</shape>

dot_select.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@android:color/holo_red_light"></solid>

    <size android:height="10dp"
        android:width="10dp"></size>

</shape>

MainActivity佈局檔案

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.imagebanner.MainActivity">

    <com.imagebanner.ImageBannerFrameLayout
        android:id="@+id/image_banner"
        android:layout_width="match_parent"
        android:layout_height="200dp"></com.imagebanner.ImageBannerFrameLayout>
</RelativeLayout>

相關推薦

定義ViewGroupFrameLayout實現包括底部圓點

廣告輪播圖在現在的APP首頁比較常見,主要的實現方式有兩種,一種是通過ViewPager,一種是通過自定義ViewGroup。前者的實現方式比較簡便,本篇文章講的是第二種方法,有人說用ViewPager不是更方便嗎,的確,但是我們通過自己定義ViewGroup,

定義ViewGroup繼承FrameLayout 實現下拉重新整理功能

程式碼不多,註釋也不多,因為比較簡單 效果圖 貼程式碼 activity_refresh_head.xml  (下拉重新整理的載入框) <?xml version="1.0" encoding="utf-8"?> <LinearLayout x

Jquery純JS實現--左右切換式

var cur = 0, //當前的圖片序號 imgLis = getElementsByClassName("imgList")[0].getElementsByTagName("li"), //獲取圖片組 imgLen = imgLis.length, //獲取圖片的

Jquery純JS實現--淡入淡出切換式

之前有寫過一篇輪播圖,是左右切換式的,可以參考 Jquery和純JS實現輪播圖(一)–左右切換式 今天分享一下淡入淡出式的輪播圖,同樣也是用純js和Jquery兩種方法來實現: JQUERY實現

BannerImageLoder無限精簡版

1.首先先在程式中匯入我們要使的依賴 implementation 'com.youth.banner:banner:1.4.9' implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'

Android框架之路——Banner實現RecyclerView新增Header

一、簡介 Banner能實現迴圈播放多個廣告圖片和手動滑動迴圈等功能。因為原生ViewPager並不支援迴圈翻頁, 要實現迴圈還得需要自己去動手。Banner框架可以進行不同樣式、不同動畫設定, 以及完善的api方法能滿足大部分軟體首頁輪播圖效果的需求。

JS原生程式碼實現無左右滑動,底下圓點按鈕

先上效果圖: 由於動態圖太大,所以只能截圖了;大致效果,圖片輪播的過程中,底下的灰色提示漸漸出現; 現在來說實現思路:        HTML部分: <div id="content"> <img id="img1" /> &

用javaScript實現效果 包括自動變換,按鈕控制,上一張下一張切換

1.HTML程式碼 <div id="wrap"> <img src="images/1.jpg" alt="" class="on"> <img src="images/2.jpg" alt=""> <im

Android 實現效果利用開源控制元件

首先匯入依賴 implementation 'com.youth.banner:banner:1.4.10' 在佈局檔案中新

JQ多張同時顯示

HTML: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <

js面向物件---無縫附面向過程程式碼

window.onload = function(){ var t1 = new Lb("box"); t1.l(); //設定包含圖片的ul寬度 t1.dot(); //點哪個圓,相對應的圖片顯示

Android Viewpager+Handler定時器

發現好多人提到banner,第一個想法就是擼個第三方依賴。然後出bug了,開啟三方程式碼,一堆檔案無從下手,改了又擔心出現新bug,然後又替換了第二個三方… 一個ViewPager能實現的功能,何必求助第三方。 Banner的實現技術點主要在於 1

Android 定義實現實踐

開發十年,就只剩下這套架構體系了! >>>   

基於springboot通過定義註解AOP實現許可權驗證

這篇文章主要介紹自定義註解配合AOP的使用來完成一個簡單的許可權驗證的功能。 一、移入依賴 <parent> <groupId>org.springframework.boot</groupId> <artifactId>sprin

定義banner

public class MyBannerActivity extends AppCompatActivity { private String[] picUrl = { "https://img.huxiucdn.com/article/c

Android 定義ViewGroup 實戰篇 -> 實現FlowLayout

1、概述上一篇已經基本給大家介紹瞭如何自定義ViewGroup,如果你還不瞭解,請檢視:,本篇將使用上篇介紹的方法,給大家帶來一個例項:實現FlowLayout,何為FlowLayout,如果對Java的Swing比較熟悉的話一定不會陌生,就是控制元件根據ViewGroup的

定義ViewGroupView的MotionEvent的關係

當TouchEvent發生時,首先Activity將TouchEvent傳遞給最頂層的ViewGroup, TouchEvent最先到達最頂層 viewGroup 的 dispatchTouchEvent ,然後由  dispatchTouchEvent 方法進行分發,

springboot rabbitMQ 定義MessageConverterClassMapper實現訊息序列化

背景:公司專案使用springboot + rabbitMQ 處理訂單和推送訊息,最開始的時候,producer都是直接convertAndSend的json資料, consumer也是接收json資料,然後在轉化為Bean去處理邏輯。當然,這樣雖然沒啥大問題,但是感覺很麻煩,後來查閱文件,

詳解vue之better-scroll實現頁面滾動

(該方法只針對移動端使用效果較好,PC端不推薦,使用的版本是[email protected],其他版本會出錯) 1.安裝better-scroll 在根目錄中package.json的dependencies中新增: "better-scr

通過jQueryBootstrap來分別實現

一、通過Bootstrap來實現輪播圖 準備好Bootstrap所需的包,輪播圖所需的圖片,然後就可以開始來寫輪播圖了。 <div class="container"> <div class="row"> <div cl