├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ └── universal-image-loader-1.9.4-with-sources.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── hmy │ │ └── ninegridlayout │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── hmy │ │ │ └── ninegridlayout │ │ │ ├── AppApplication.java │ │ │ ├── MainActivity.java │ │ │ ├── RecyclerViewExampleActivity.java │ │ │ ├── adapter │ │ │ ├── NineGridTest2Adapter.java │ │ │ └── NineGridTestAdapter.java │ │ │ ├── model │ │ │ └── NineGridTestModel.java │ │ │ ├── util │ │ │ └── ImageLoaderUtil.java │ │ │ └── view │ │ │ ├── ListViewExampleActivity.java │ │ │ ├── NineGridLayout.java │ │ │ ├── NineGridTestLayout.java │ │ │ └── RatioImageView.java │ └── res │ │ ├── drawable-xhdpi │ │ └── banner_default.png │ │ ├── layout │ │ ├── activity_list_view_example.xml │ │ ├── activity_main.xml │ │ ├── activity_recycler_view_example.xml │ │ └── item_bbs_nine_grid.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── hmy │ └── ninegridlayout │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── imageCache ├── GIF.gif ├── img1.png ├── img2.png ├── img3.png ├── img4.png └── img5.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | NineGridLayout -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NineGridLayout 2 | 一个仿微信朋友圈和QQ空间的九宫格图片展示自定义控件。 3 | 4 | CSDN:[http://blog.csdn.net/hmyang314/article/details/51415396](http://blog.csdn.net/hmyang314/article/details/51415396) 5 | 6 | ![](https://github.com/HMY314/NineGridLayout/blob/master/imageCache/GIF.gif) 7 | 8 | ---------- 9 | ## 一、介绍 10 | 1、当只有1张图时,可以自己定制图片宽高,也可以使用默认九宫格的宽高; 11 | 2、当只有4张图时,以2*2的方式显示; 12 | 3、除以上两种情况下,都是按照3列方式显示,但这时有一些细节: 13 | a、如果只有9张图,当然是以3*3的方式显示; 14 | b、如果超过9张图,可以设置是否全部显示。 15 | 如果设置不完全显示,则按照3*3的方式显示,但是在第9张图上会有一个带“+”号的数字, 16 | 代表还有几张没有显示,这里是模仿了QQ空间图片超出9张的显示方式; 17 | 如果设置全部显示,理所当然的将所有图片都显示出来。 18 | 4、图片被按下时,会有一个变暗的效果,这也是模仿微信朋友圈的效果。 19 | 20 | ---------- 21 | ## 二、使用方法 22 | 23 | 1、核心类是NineGridLayout,继承自ViewGroup的抽象类,所以我们实际项目使用需要继承它,并要实现3个方法,如下: 24 | 25 | public abstract class NineGridLayout extends ViewGroup { 26 | //******************************其他代码省略************************** 27 | 28 | /** 29 | * 显示一张图片 30 | * @param imageView 31 | * @param url 32 | * @param parentWidth 父控件宽度 33 | * @return true 代表按照九宫格默认大小显示,false 代表按照自定义宽高显示 34 | */ 35 | protected abstract boolean displayOneImage(RatioImageView imageView, String url, int parentWidth); 36 | 37 | protected abstract void displayImage(RatioImageView imageView, String url); 38 | 39 | /** 40 | * 点击图片时执行 41 | */ 42 | protected abstract void onClickImage(int position, String url, List urlList); 43 | } 44 | 45 | ---------- 46 | 2、我这里用NineGridTestLayout继承NineGridLayout实现,displayOneImage()与displayImage()中的参数都是显示图片需要的,我这里用的是ImageLoader显示图片,当然你也可以用其他的。 47 | 48 | public class NineGridTestLayout extends NineGridLayout { 49 | 50 | protected static final int MAX_W_H_RATIO = 3; 51 | 52 | public NineGridTestLayout(Context context) { 53 | super(context); 54 | } 55 | 56 | public NineGridTestLayout(Context context, AttributeSet attrs) { 57 | super(context, attrs); 58 | } 59 | 60 | @Override 61 | protected boolean displayOneImage(final RatioImageView imageView, String url, final int parentWidth) { 62 | 63 | //这里是只显示一张图片的情况,显示图片的宽高可以根据实际图片大小自由定制,parentWidth 为该layout的宽度 64 | ImageLoader.getInstance().displayImage(imageView, url, ImageLoaderUtil.getPhotoImageOption(), new ImageLoadingListener() { 65 | @Override 66 | public void onLoadingStarted(String imageUri, View view) { 67 | 68 | } 69 | 70 | @Override 71 | public void onLoadingFailed(String imageUri, View view, FailReason failReason) { 72 | 73 | } 74 | 75 | @Override 76 | public void onLoadingComplete(String imageUri, View view, Bitmap bitmap) { 77 | int w = bitmap.getWidth(); 78 | int h = bitmap.getHeight(); 79 | 80 | int newW; 81 | int newH; 82 | if (h > w * MAX_W_H_RATIO) {//h:w = 5:3 83 | newW = parentWidth / 2; 84 | newH = newW * 5 / 3; 85 | } else if (h < w) {//h:w = 2:3 86 | newW = parentWidth * 2 / 3; 87 | newH = newW * 2 / 3; 88 | } else {//newH:h = newW :w 89 | newW = parentWidth / 2; 90 | newH = h * newW / w; 91 | } 92 | setOneImageLayoutParams(imageView, newW, newH); 93 | } 94 | 95 | @Override 96 | public void onLoadingCancelled(String imageUri, View view) { 97 | 98 | } 99 | }); 100 | return false;// true 代表按照九宫格默认大小显示(此时不要调用setOneImageLayoutParams);false 代表按照自定义宽高显示。 101 | } 102 | 103 | @Override 104 | protected void displayImage(RatioImageView imageView, String url) { 105 | ImageLoaderUtil.getImageLoader(mContext).displayImage(url, imageView, ImageLoaderUtil.getPhotoImageOption()); 106 | } 107 | 108 | @Override 109 | protected void onClickImage(int i, String url, List urlList) { 110 | Toast.makeText(mContext, "点击了图片" + url, Toast.LENGTH_SHORT).show(); 111 | } 112 | } 113 | 114 | ---------- 115 | 3、在xml中实现 116 | 117 | 123 | 124 | app:sapcing是设置九宫格中图片之间的间隔。 125 | 126 | ---------- 127 | 4、使用: 128 | 129 | List urlList = new ArrayList<>();//图片url 130 | NineGridTestLayout layout = (NineGridTestLayout) view.findViewById(R.id.layout_nine_grid); 131 | layout.setIsShowAll(false); //当传入的图片数超过9张时,是否全部显示 132 | layout.setSpacing(5); //动态设置图片之间的间隔 133 | layout.setUrlList(urlList); //最后再设置图片url 134 | 135 | ---------- 136 | 5、效果图 137 | 138 | ![](https://github.com/HMY314/NineGridLayout/blob/master/imageCache/img1.png) 139 | ![](https://github.com/HMY314/NineGridLayout/blob/master/imageCache/img2.png) 140 | ![](https://github.com/HMY314/NineGridLayout/blob/master/imageCache/img3.png) 141 | ![](https://github.com/HMY314/NineGridLayout/blob/master/imageCache/img4.png) 142 | ![](https://github.com/HMY314/NineGridLayout/blob/master/imageCache/img5.png) 143 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.hmy.ninegridlayout" 9 | minSdkVersion 9 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.1.1' 26 | compile 'com.android.support:recyclerview-v7:23.1.1' 27 | } 28 | -------------------------------------------------------------------------------- /app/libs/universal-image-loader-1.9.4-with-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMY314/NineGridLayout/0cb5c796ac9811919d437fcd7ed408556cdc7f53/app/libs/universal-image-loader-1.9.4-with-sources.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in G:\soft\AndroidStudio\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/hmy/ninegridlayout/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/AppApplication.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout; 2 | 3 | import android.app.Application; 4 | 5 | import com.nostra13.universalimageloader.core.ImageLoader; 6 | import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; 7 | 8 | /** 9 | * 描述: 10 | * 作者:HMY 11 | * 时间:2016/5/13 12 | */ 13 | public class AppApplication extends Application { 14 | 15 | @Override 16 | public void onCreate() { 17 | super.onCreate(); 18 | initImageLoader(); 19 | } 20 | 21 | private void initImageLoader() { 22 | ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this); 23 | ImageLoader.getInstance().init(configuration); 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.ListView; 7 | 8 | import com.hmy.ninegridlayout.adapter.NineGridTestAdapter; 9 | import com.hmy.ninegridlayout.model.NineGridTestModel; 10 | import com.hmy.ninegridlayout.view.ListViewExampleActivity; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | private List mList = new ArrayList<>(); 18 | private String[] mUrls = new String[]{"http://d.hiphotos.baidu.com/image/h%3D200/sign=201258cbcd80653864eaa313a7dca115/ca1349540923dd54e54f7aedd609b3de9c824873.jpg", 19 | "http://img3.fengniao.com/forum/attachpics/537/165/21472986.jpg", 20 | "http://d.hiphotos.baidu.com/image/h%3D200/sign=ea218b2c5566d01661199928a729d498/a08b87d6277f9e2fd4f215e91830e924b999f308.jpg", 21 | "http://img4.imgtn.bdimg.com/it/u=3445377427,2645691367&fm=21&gp=0.jpg", 22 | "http://img4.imgtn.bdimg.com/it/u=2644422079,4250545639&fm=21&gp=0.jpg", 23 | "http://img5.imgtn.bdimg.com/it/u=1444023808,3753293381&fm=21&gp=0.jpg", 24 | "http://img4.imgtn.bdimg.com/it/u=882039601,2636712663&fm=21&gp=0.jpg", 25 | "http://img4.imgtn.bdimg.com/it/u=4119861953,350096499&fm=21&gp=0.jpg", 26 | "http://img5.imgtn.bdimg.com/it/u=2437456944,1135705439&fm=21&gp=0.jpg", 27 | "http://img2.imgtn.bdimg.com/it/u=3251359643,4211266111&fm=21&gp=0.jpg", 28 | "http://img4.duitang.com/uploads/item/201506/11/20150611000809_yFe5Z.jpeg", 29 | "http://img5.imgtn.bdimg.com/it/u=1717647885,4193212272&fm=21&gp=0.jpg", 30 | "http://img5.imgtn.bdimg.com/it/u=2024625579,507531332&fm=21&gp=0.jpg"}; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_main); 36 | 37 | initData(); 38 | } 39 | 40 | private void initData() { 41 | initListData(); 42 | } 43 | 44 | public void click(View view) { 45 | switch (view.getId()) { 46 | case R.id.btn_RecyclerViewExample: 47 | RecyclerViewExampleActivity.startActivity(this, mList); 48 | break; 49 | case R.id.btn_ListViewExample: 50 | ListViewExampleActivity.startActivity(this, mList); 51 | break; 52 | } 53 | } 54 | 55 | private void initListData() { 56 | NineGridTestModel model1 = new NineGridTestModel(); 57 | model1.urlList.add(mUrls[0]); 58 | mList.add(model1); 59 | 60 | NineGridTestModel model2 = new NineGridTestModel(); 61 | model2.urlList.add(mUrls[4]); 62 | mList.add(model2); 63 | // 64 | // NineGridTestModel model3 = new NineGridTestModel(); 65 | // model3.urlList.add(mUrls[2]); 66 | // mList.add(model3); 67 | 68 | NineGridTestModel model4 = new NineGridTestModel(); 69 | for (int i = 0; i < mUrls.length; i++) { 70 | model4.urlList.add(mUrls[i]); 71 | } 72 | model4.isShowAll = false; 73 | mList.add(model4); 74 | 75 | NineGridTestModel model5 = new NineGridTestModel(); 76 | for (int i = 0; i < mUrls.length; i++) { 77 | model5.urlList.add(mUrls[i]); 78 | } 79 | model5.isShowAll = true;//显示全部图片 80 | mList.add(model5); 81 | 82 | NineGridTestModel model6 = new NineGridTestModel(); 83 | for (int i = 0; i < 9; i++) { 84 | model6.urlList.add(mUrls[i]); 85 | } 86 | mList.add(model6); 87 | 88 | NineGridTestModel model7 = new NineGridTestModel(); 89 | for (int i = 3; i < 7; i++) { 90 | model7.urlList.add(mUrls[i]); 91 | } 92 | mList.add(model7); 93 | 94 | NineGridTestModel model8 = new NineGridTestModel(); 95 | for (int i = 3; i < 6; i++) { 96 | model8.urlList.add(mUrls[i]); 97 | } 98 | mList.add(model8); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/RecyclerViewExampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | 10 | import com.hmy.ninegridlayout.adapter.NineGridTest2Adapter; 11 | import com.hmy.ninegridlayout.model.NineGridTestModel; 12 | 13 | import java.io.Serializable; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | public class RecyclerViewExampleActivity extends AppCompatActivity { 18 | 19 | private static final String ARG_LIST = "list"; 20 | 21 | private RecyclerView mRecyclerView; 22 | private RecyclerView.LayoutManager mLayoutManager; 23 | private NineGridTest2Adapter mAdapter; 24 | 25 | private List mList; 26 | 27 | public static void startActivity(Context context, List list) { 28 | Intent intent = new Intent(context, RecyclerViewExampleActivity.class); 29 | intent.putExtra(ARG_LIST, (Serializable) list); 30 | context.startActivity(intent); 31 | } 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_recycler_view_example); 37 | 38 | getIntentData(); 39 | initView(); 40 | } 41 | 42 | private void getIntentData() { 43 | mList = (List) getIntent().getSerializableExtra(ARG_LIST); 44 | } 45 | 46 | private void initView() { 47 | mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); 48 | 49 | mLayoutManager = new LinearLayoutManager(this); 50 | mRecyclerView.setLayoutManager(mLayoutManager); 51 | 52 | mAdapter = new NineGridTest2Adapter(this); 53 | mAdapter.setList(mList); 54 | mRecyclerView.setAdapter(mAdapter); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/adapter/NineGridTest2Adapter.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.hmy.ninegridlayout.R; 10 | import com.hmy.ninegridlayout.model.NineGridTestModel; 11 | import com.hmy.ninegridlayout.view.NineGridTestLayout; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * Created by HMY on 2016/8/6 17 | */ 18 | public class NineGridTest2Adapter extends RecyclerView.Adapter { 19 | 20 | private Context mContext; 21 | private List mList; 22 | protected LayoutInflater inflater; 23 | 24 | public NineGridTest2Adapter(Context context) { 25 | mContext = context; 26 | inflater = LayoutInflater.from(context); 27 | } 28 | 29 | public void setList(List list) { 30 | mList = list; 31 | } 32 | 33 | @Override 34 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 35 | View convertView = inflater.inflate(R.layout.item_bbs_nine_grid, parent, false); 36 | ViewHolder viewHolder = new ViewHolder(convertView); 37 | return viewHolder; 38 | } 39 | 40 | @Override 41 | public void onBindViewHolder(ViewHolder holder, int position) { 42 | holder.layout.setIsShowAll(mList.get(position).isShowAll); 43 | holder.layout.setUrlList(mList.get(position).urlList); 44 | } 45 | 46 | @Override 47 | public int getItemCount() { 48 | return getListSize(mList); 49 | } 50 | 51 | public class ViewHolder extends RecyclerView.ViewHolder { 52 | NineGridTestLayout layout; 53 | 54 | public ViewHolder(View itemView) { 55 | super(itemView); 56 | layout = (NineGridTestLayout) itemView.findViewById(R.id.layout_nine_grid); 57 | } 58 | } 59 | 60 | private int getListSize(List list) { 61 | if (list == null || list.size() == 0) { 62 | return 0; 63 | } 64 | return list.size(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/adapter/NineGridTestAdapter.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.BaseAdapter; 8 | 9 | import com.hmy.ninegridlayout.R; 10 | import com.hmy.ninegridlayout.model.NineGridTestModel; 11 | import com.hmy.ninegridlayout.view.NineGridTestLayout; 12 | 13 | import java.util.List; 14 | 15 | 16 | /** 17 | * 描述: 18 | * 作者:HMY 19 | * 时间:2016/5/13 20 | */ 21 | public class NineGridTestAdapter extends BaseAdapter { 22 | 23 | private Context mContext; 24 | private List mList; 25 | protected LayoutInflater inflater; 26 | 27 | public NineGridTestAdapter(Context context) { 28 | mContext = context; 29 | inflater = LayoutInflater.from(context); 30 | } 31 | 32 | public void setList(List list) { 33 | mList = list; 34 | } 35 | 36 | @Override 37 | public int getCount() { 38 | return getListSize(mList); 39 | } 40 | 41 | @Override 42 | public Object getItem(int position) { 43 | return mList.get(position); 44 | } 45 | 46 | @Override 47 | public long getItemId(int position) { 48 | return position; 49 | } 50 | 51 | @Override 52 | public View getView(int position, View convertView, ViewGroup parent) { 53 | 54 | ViewHolder holder = null; 55 | if (convertView == null || convertView.getTag() == null) { 56 | convertView = inflater.inflate(R.layout.item_bbs_nine_grid, parent, false); 57 | holder = new ViewHolder(convertView); 58 | convertView.setTag(holder); 59 | } else { 60 | holder = (ViewHolder) convertView.getTag(); 61 | } 62 | 63 | holder.layout.setIsShowAll(mList.get(position).isShowAll); 64 | holder.layout.setUrlList(mList.get(position).urlList); 65 | 66 | return convertView; 67 | } 68 | 69 | private class ViewHolder { 70 | NineGridTestLayout layout; 71 | 72 | public ViewHolder(View view) { 73 | layout = (NineGridTestLayout) view.findViewById(R.id.layout_nine_grid); 74 | } 75 | } 76 | 77 | private int getListSize(List list) { 78 | if (list == null || list.size() == 0) { 79 | return 0; 80 | } 81 | return list.size(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/model/NineGridTestModel.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout.model; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | /** 8 | * 描述: 9 | * 作者:HMY 10 | * 时间:2016/5/13 11 | */ 12 | public class NineGridTestModel implements Serializable { 13 | private static final long serialVersionUID = 2189052605715370758L; 14 | 15 | public List urlList = new ArrayList<>(); 16 | 17 | public boolean isShowAll = false; 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/util/ImageLoaderUtil.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout.util; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.widget.ImageView; 6 | 7 | import com.hmy.ninegridlayout.R; 8 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 9 | import com.nostra13.universalimageloader.core.ImageLoader; 10 | import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; 11 | 12 | /** 13 | * Created by HMY 14 | */ 15 | public class ImageLoaderUtil { 16 | 17 | public static ImageLoader getImageLoader(Context context) { 18 | return ImageLoader.getInstance(); 19 | } 20 | 21 | public static DisplayImageOptions getPhotoImageOption() { 22 | Integer extra = 1; 23 | DisplayImageOptions options = new DisplayImageOptions.Builder().cacheInMemory(true).cacheOnDisk(true) 24 | .showImageForEmptyUri(R.drawable.banner_default).showImageOnFail(R.drawable.banner_default) 25 | .showImageOnLoading(R.drawable.banner_default) 26 | .extraForDownloader(extra) 27 | .bitmapConfig(Bitmap.Config.RGB_565).build(); 28 | return options; 29 | } 30 | 31 | public static void displayImage(Context context, ImageView imageView, String url, DisplayImageOptions options) { 32 | getImageLoader(context).displayImage(url, imageView, options); 33 | } 34 | 35 | public static void displayImage(Context context, ImageView imageView, String url, DisplayImageOptions options, ImageLoadingListener listener) { 36 | getImageLoader(context).displayImage(url, imageView, options, listener); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/view/ListViewExampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout.view; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | import android.widget.ListView; 8 | 9 | import com.hmy.ninegridlayout.R; 10 | import com.hmy.ninegridlayout.adapter.NineGridTestAdapter; 11 | import com.hmy.ninegridlayout.model.NineGridTestModel; 12 | 13 | import java.io.Serializable; 14 | import java.util.List; 15 | 16 | public class ListViewExampleActivity extends AppCompatActivity { 17 | 18 | private static final String ARG_LIST = "list"; 19 | 20 | private ListView mListView; 21 | private NineGridTestAdapter mAdapter; 22 | private List mList; 23 | 24 | public static void startActivity(Context context, List list) { 25 | Intent intent = new Intent(context, ListViewExampleActivity.class); 26 | intent.putExtra(ARG_LIST, (Serializable) list); 27 | context.startActivity(intent); 28 | } 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_list_view_example); 34 | getIntentData(); 35 | initView(); 36 | } 37 | 38 | private void getIntentData() { 39 | mList = (List) getIntent().getSerializableExtra(ARG_LIST); 40 | } 41 | 42 | private void initView() { 43 | mListView = (ListView) findViewById(R.id.lv_bbs); 44 | 45 | mAdapter = new NineGridTestAdapter(this); 46 | mAdapter.setList(mList); 47 | mListView.setAdapter(mAdapter); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/view/NineGridLayout.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | import android.view.Gravity; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ImageView; 12 | import android.widget.TextView; 13 | 14 | 15 | import com.hmy.ninegridlayout.R; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.TimerTask; 20 | 21 | /** 22 | * 描述: 23 | * 作者:HMY 24 | * 时间:2016/5/10 25 | */ 26 | public abstract class NineGridLayout extends ViewGroup { 27 | 28 | private static final float DEFUALT_SPACING = 3f; 29 | private static final int MAX_COUNT = 9; 30 | 31 | protected Context mContext; 32 | private float mSpacing = DEFUALT_SPACING; 33 | private int mColumns; 34 | private int mRows; 35 | private int mTotalWidth; 36 | private int mSingleWidth; 37 | 38 | private boolean mIsShowAll = false; 39 | private boolean mIsFirst = true; 40 | private List mUrlList = new ArrayList<>(); 41 | 42 | public NineGridLayout(Context context) { 43 | super(context); 44 | init(context); 45 | } 46 | 47 | public NineGridLayout(Context context, AttributeSet attrs) { 48 | super(context, attrs); 49 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NineGridLayout); 50 | 51 | mSpacing = typedArray.getDimension(R.styleable.NineGridLayout_sapcing, DEFUALT_SPACING); 52 | typedArray.recycle(); 53 | init(context); 54 | } 55 | 56 | private void init(Context context) { 57 | mContext = context; 58 | if (getListSize(mUrlList) == 0) { 59 | setVisibility(GONE); 60 | } 61 | } 62 | 63 | @Override 64 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 65 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 66 | } 67 | 68 | @Override 69 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 70 | mTotalWidth = right - left; 71 | mSingleWidth = (int) ((mTotalWidth - mSpacing * (3 - 1)) / 3); 72 | if (mIsFirst) { 73 | notifyDataSetChanged(); 74 | mIsFirst = false; 75 | } 76 | } 77 | 78 | /** 79 | * 设置间隔 80 | * 81 | * @param spacing 82 | */ 83 | public void setSpacing(float spacing) { 84 | mSpacing = spacing; 85 | } 86 | 87 | /** 88 | * 设置是否显示所有图片(超过最大数时) 89 | * 90 | * @param isShowAll 91 | */ 92 | public void setIsShowAll(boolean isShowAll) { 93 | mIsShowAll = isShowAll; 94 | } 95 | 96 | public void setUrlList(List urlList) { 97 | if (getListSize(urlList) == 0) { 98 | setVisibility(GONE); 99 | return; 100 | } 101 | setVisibility(VISIBLE); 102 | 103 | mUrlList.clear(); 104 | mUrlList.addAll(urlList); 105 | 106 | if (!mIsFirst) { 107 | notifyDataSetChanged(); 108 | } 109 | } 110 | 111 | public void notifyDataSetChanged() { 112 | post(new TimerTask() { 113 | @Override 114 | public void run() { 115 | refresh(); 116 | } 117 | }); 118 | } 119 | 120 | private void refresh() { 121 | removeAllViews(); 122 | int size = getListSize(mUrlList); 123 | if (size > 0) { 124 | setVisibility(VISIBLE); 125 | } else { 126 | setVisibility(GONE); 127 | } 128 | 129 | if (size == 1) { 130 | String url = mUrlList.get(0); 131 | RatioImageView imageView = createImageView(0, url); 132 | 133 | //避免在ListView中一张图未加载成功时,布局高度受其他item影响 134 | LayoutParams params = getLayoutParams(); 135 | params.height = mSingleWidth; 136 | setLayoutParams(params); 137 | imageView.layout(0, 0, mSingleWidth, mSingleWidth); 138 | 139 | boolean isShowDefualt = displayOneImage(imageView, url, mTotalWidth); 140 | if (isShowDefualt) { 141 | layoutImageView(imageView, 0, url, false); 142 | } else { 143 | addView(imageView); 144 | } 145 | return; 146 | } 147 | 148 | generateChildrenLayout(size); 149 | layoutParams(); 150 | 151 | for (int i = 0; i < size; i++) { 152 | String url = mUrlList.get(i); 153 | RatioImageView imageView; 154 | if (!mIsShowAll) { 155 | if (i < MAX_COUNT - 1) { 156 | imageView = createImageView(i, url); 157 | layoutImageView(imageView, i, url, false); 158 | } else { //第9张时 159 | if (size <= MAX_COUNT) {//刚好第9张 160 | imageView = createImageView(i, url); 161 | layoutImageView(imageView, i, url, false); 162 | } else {//超过9张 163 | imageView = createImageView(i, url); 164 | layoutImageView(imageView, i, url, true); 165 | break; 166 | } 167 | } 168 | } else { 169 | imageView = createImageView(i, url); 170 | layoutImageView(imageView, i, url, false); 171 | } 172 | } 173 | } 174 | 175 | private void layoutParams() { 176 | int singleHeight = mSingleWidth; 177 | 178 | //根据子view数量确定高度 179 | LayoutParams params = getLayoutParams(); 180 | params.height = (int) (singleHeight * mRows + mSpacing * (mRows - 1)); 181 | setLayoutParams(params); 182 | } 183 | 184 | private RatioImageView createImageView(final int i, final String url) { 185 | RatioImageView imageView = new RatioImageView(mContext); 186 | imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); 187 | imageView.setOnClickListener(new OnClickListener() { 188 | @Override 189 | public void onClick(View v) { 190 | onClickImage(i, url, mUrlList); 191 | } 192 | }); 193 | return imageView; 194 | } 195 | 196 | /** 197 | * @param imageView 198 | * @param url 199 | * @param showNumFlag 是否在最大值的图片上显示还有未显示的图片张数 200 | */ 201 | private void layoutImageView(RatioImageView imageView, int i, String url, boolean showNumFlag) { 202 | final int singleWidth = (int) ((mTotalWidth - mSpacing * (3 - 1)) / 3); 203 | int singleHeight = singleWidth; 204 | 205 | int[] position = findPosition(i); 206 | int left = (int) ((singleWidth + mSpacing) * position[1]); 207 | int top = (int) ((singleHeight + mSpacing) * position[0]); 208 | int right = left + singleWidth; 209 | int bottom = top + singleHeight; 210 | 211 | imageView.layout(left, top, right, bottom); 212 | 213 | addView(imageView); 214 | if (showNumFlag) {//添加超过最大显示数量的文本 215 | int overCount = getListSize(mUrlList) - MAX_COUNT; 216 | if (overCount > 0) { 217 | float textSize = 30; 218 | final TextView textView = new TextView(mContext); 219 | textView.setText("+" + String.valueOf(overCount)); 220 | textView.setTextColor(Color.WHITE); 221 | textView.setPadding(0, singleHeight / 2 - getFontHeight(textSize), 0, 0); 222 | textView.setTextSize(textSize); 223 | textView.setGravity(Gravity.CENTER); 224 | textView.setBackgroundColor(Color.BLACK); 225 | textView.getBackground().setAlpha(120); 226 | 227 | textView.layout(left, top, right, bottom); 228 | addView(textView); 229 | } 230 | } 231 | displayImage(imageView, url); 232 | } 233 | 234 | private int[] findPosition(int childNum) { 235 | int[] position = new int[2]; 236 | for (int i = 0; i < mRows; i++) { 237 | for (int j = 0; j < mColumns; j++) { 238 | if ((i * mColumns + j) == childNum) { 239 | position[0] = i;//行 240 | position[1] = j;//列 241 | break; 242 | } 243 | } 244 | } 245 | return position; 246 | } 247 | 248 | /** 249 | * 根据图片个数确定行列数量 250 | * 251 | * @param length 252 | */ 253 | private void generateChildrenLayout(int length) { 254 | if (length <= 3) { 255 | mRows = 1; 256 | mColumns = length; 257 | } else if (length <= 6) { 258 | mRows = 2; 259 | mColumns = 3; 260 | if (length == 4) { 261 | mColumns = 2; 262 | } 263 | } else { 264 | mColumns = 3; 265 | if (mIsShowAll) { 266 | mRows = length / 3; 267 | int b = length % 3; 268 | if (b > 0) { 269 | mRows++; 270 | } 271 | } else { 272 | mRows = 3; 273 | } 274 | } 275 | 276 | } 277 | 278 | protected void setOneImageLayoutParams(RatioImageView imageView, int width, int height) { 279 | imageView.setLayoutParams(new LayoutParams(width, height)); 280 | imageView.layout(0, 0, width, height); 281 | 282 | LayoutParams params = getLayoutParams(); 283 | // params.width = width; 284 | params.height = height; 285 | setLayoutParams(params); 286 | } 287 | 288 | private int getListSize(List list) { 289 | if (list == null || list.size() == 0) { 290 | return 0; 291 | } 292 | return list.size(); 293 | } 294 | 295 | private int getFontHeight(float fontSize) { 296 | Paint paint = new Paint(); 297 | paint.setTextSize(fontSize); 298 | Paint.FontMetrics fm = paint.getFontMetrics(); 299 | return (int) Math.ceil(fm.descent - fm.ascent); 300 | } 301 | 302 | /** 303 | * @param imageView 304 | * @param url 305 | * @param parentWidth 父控件宽度 306 | * @return true 代表按照九宫格默认大小显示,false 代表按照自定义宽高显示 307 | */ 308 | protected abstract boolean displayOneImage(RatioImageView imageView, String url, int parentWidth); 309 | 310 | protected abstract void displayImage(RatioImageView imageView, String url); 311 | 312 | protected abstract void onClickImage(int position, String url, List urlList); 313 | } 314 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/view/NineGridTestLayout.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.Toast; 8 | 9 | import com.hmy.ninegridlayout.util.ImageLoaderUtil; 10 | import com.nostra13.universalimageloader.core.assist.FailReason; 11 | import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * 描述: 17 | * 作者:HMY 18 | * 时间:2016/5/12 19 | */ 20 | public class NineGridTestLayout extends NineGridLayout { 21 | 22 | protected static final int MAX_W_H_RATIO = 3; 23 | 24 | public NineGridTestLayout(Context context) { 25 | super(context); 26 | } 27 | 28 | public NineGridTestLayout(Context context, AttributeSet attrs) { 29 | super(context, attrs); 30 | } 31 | 32 | @Override 33 | protected boolean displayOneImage(final RatioImageView imageView, String url, final int parentWidth) { 34 | 35 | ImageLoaderUtil.displayImage(mContext, imageView, url, ImageLoaderUtil.getPhotoImageOption(), new ImageLoadingListener() { 36 | @Override 37 | public void onLoadingStarted(String imageUri, View view) { 38 | 39 | } 40 | 41 | @Override 42 | public void onLoadingFailed(String imageUri, View view, FailReason failReason) { 43 | 44 | } 45 | 46 | @Override 47 | public void onLoadingComplete(String imageUri, View view, Bitmap bitmap) { 48 | int w = bitmap.getWidth(); 49 | int h = bitmap.getHeight(); 50 | 51 | int newW; 52 | int newH; 53 | if (h > w * MAX_W_H_RATIO) {//h:w = 5:3 54 | newW = parentWidth / 2; 55 | newH = newW * 5 / 3; 56 | } else if (h < w) {//h:w = 2:3 57 | newW = parentWidth * 2 / 3; 58 | newH = newW * 2 / 3; 59 | } else {//newH:h = newW :w 60 | newW = parentWidth / 2; 61 | newH = h * newW / w; 62 | } 63 | setOneImageLayoutParams(imageView, newW, newH); 64 | } 65 | 66 | @Override 67 | public void onLoadingCancelled(String imageUri, View view) { 68 | 69 | } 70 | }); 71 | return false; 72 | } 73 | 74 | @Override 75 | protected void displayImage(RatioImageView imageView, String url) { 76 | ImageLoaderUtil.getImageLoader(mContext).displayImage(url, imageView, ImageLoaderUtil.getPhotoImageOption()); 77 | } 78 | 79 | @Override 80 | protected void onClickImage(int i, String url, List urlList) { 81 | Toast.makeText(mContext, "点击了图片" + url, Toast.LENGTH_SHORT).show(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/hmy/ninegridlayout/view/RatioImageView.java: -------------------------------------------------------------------------------- 1 | package com.hmy.ninegridlayout.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.graphics.PorterDuff; 7 | import android.graphics.drawable.Drawable; 8 | import android.util.AttributeSet; 9 | import android.view.MotionEvent; 10 | import android.widget.ImageView; 11 | 12 | import com.hmy.ninegridlayout.R; 13 | 14 | 15 | /** 16 | * 根据宽高比例自动计算高度ImageView 17 | * Created by HMY on 2016/4/21. 18 | */ 19 | public class RatioImageView extends ImageView { 20 | 21 | /** 22 | * 宽高比例 23 | */ 24 | private float mRatio = 0f; 25 | 26 | public RatioImageView(Context context, AttributeSet attrs, int defStyleAttr) { 27 | super(context, attrs, defStyleAttr); 28 | } 29 | 30 | public RatioImageView(Context context, AttributeSet attrs) { 31 | super(context, attrs); 32 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RatioImageView); 33 | 34 | mRatio = typedArray.getFloat(R.styleable.RatioImageView_ratio, 0f); 35 | typedArray.recycle(); 36 | } 37 | 38 | public RatioImageView(Context context) { 39 | super(context); 40 | } 41 | 42 | /** 43 | * 设置ImageView的宽高比 44 | * 45 | * @param ratio 46 | */ 47 | public void setRatio(float ratio) { 48 | mRatio = ratio; 49 | } 50 | 51 | @Override 52 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 53 | int width = MeasureSpec.getSize(widthMeasureSpec); 54 | if (mRatio != 0) { 55 | float height = width / mRatio; 56 | heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) height, MeasureSpec.EXACTLY); 57 | } 58 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 59 | } 60 | 61 | @Override 62 | public boolean onTouchEvent(MotionEvent event) { 63 | 64 | switch (event.getAction()) { 65 | case MotionEvent.ACTION_DOWN: 66 | Drawable drawable = getDrawable(); 67 | if (drawable != null) { 68 | drawable.mutate().setColorFilter(Color.GRAY, 69 | PorterDuff.Mode.MULTIPLY); 70 | } 71 | break; 72 | case MotionEvent.ACTION_MOVE: 73 | break; 74 | case MotionEvent.ACTION_CANCEL: 75 | case MotionEvent.ACTION_UP: 76 | Drawable drawableUp = getDrawable(); 77 | if (drawableUp != null) { 78 | drawableUp.mutate().clearColorFilter(); 79 | } 80 | break; 81 | } 82 | 83 | return super.onTouchEvent(event); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/banner_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMY314/NineGridLayout/0cb5c796ac9811919d437fcd7ed408556cdc7f53/app/src/main/res/drawable-xhdpi/banner_default.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_list_view_example.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 |