Как стать автором
Обновить

Создание горизонтального скрола с градиентом и стрелками

Время на прочтение8 мин
Количество просмотров5.1K
Расскажу как создать горизонтальный блок, который скролится вправо/влево. У которого пропадают стрелки по краям (когда мы дошли до конца). И который имеет градиентную заливку.

image



Небольшое вступление



Не так давно пришлось с PHP начать опять программировать на Java под андроид (до этого лет 5 уже Явой почти не занимался, так получилось). На работе потребовалось создать приложение. Естественно для создания некоторых элементов пришлось лезть на http://stackoverflow.com/ и некоторые другие сайты (включая официальную документацию) но как ни странно, там нет описания нужных элементов и приходится их писать с нуля (или использовать части из других функционалов).

Что же мне необходимо сделать



В дизайне был элемент вот такого вот простого, с виду, вида:
image

Начало поисков



Сначала я решил что данный элемент будет создать очень просто. Но столкнулся с рядом проблем.
1. Градиент
2. Преобразование цветов из #rrggbb
3. Загрузка изображения из интернета для бекграунда
4. Кнопки враво-влево
5. Создание текстовых полей в горизонтальном скроле динамически

Начнем с градиента, как самого простого


Нашел огромное количество материала как сделать градиент в XML, но у меня задача заключалась в том, чтоб градиент мог меняться от одного раздела к другому. тут был найден кусок кода:

	int colors[] = { 0xff255779 , 0xff3e7492, 0xffa6c0cd };
	GradientDrawable g = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);
	setBackgroundDrawable(g);
	


Этот код создает градиент и его заливает как задний фон. Это то, что мне было необходимо. Но тут я столкнулся с тем, что иногда мне нужен не только градиент но и вставка рисунка на задний фон, причем рисунок необходимо заливать полностью.

Но об этом ниже.
Вначале необходимо преобразовать цвет из #rrggbb в 0xffa6c0cd.

Преобразование цветов из #rrggbb


Для этого была написана функция:

	public int getBackgoundColorAsInt(String bgcolor){
		try{
			int result = new BigInteger(bgcolor.replace("#", "FF"),16).intValue();
			return result;
		}catch(Exception e){
			Log.e("Error in switch color from String to int","Can't switch bg color : " + bgcolor);
			return new BigInteger("FFCCCCCC",16).intValue();
		}
	}
	


Функция преобразовывает строчку в int и возвращает нам полученное число.
С цветом разобрались. Теперь разберемся с рисунком.

Загрузка изображения из интернета для бекграунда


После длительных поисков тут была найдена функция которая устроила меня больше всего:
	public Drawable LoadImageFromWebOperations(String url) {
		try {
			InputStream is = (InputStream) new URL(url).getContent();
			Drawable d = Drawable.createFromStream(is, "src name");
			return d;
		} catch (Exception e) {
			Log.e("Error in load image", "Can't load image from url " + url);
			return null;
		}
	}
	


Все просто легко и понятно.

Кнопки враво-влево

Настало время создать XML файл для боковых частей ( справа и слева от самого скрола )
Тут я приведу только кусок кода, который у меня получился в самом конце. После кучи экспериментов.

	<?xml version="1.0" encoding="utf-8"?>
	<LinearLayout 
	    xmlns:android="http://schemas.android.com/apk/res/android"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:gravity="center_vertical"
	    android:background="#0000ff"
	    android:id="@+id/top_menu_categories_layout"
	    android:layout_weight="1"
	    android:visibility="gone"
	    >
	    <ImageView 
	        android:id="@+id/subcategory_left_scroll_img"
	        android:background="@drawable/top_menu_prev_category"
	        android:layout_height="wrap_content" 
	        android:layout_width="25dp"
	        android:visibility="invisible"
	        android:layout_weight=".15"
	    />
	        <com.lid.app.custom_views.SubcategoryScrollView
	            android:id="@+id/HorizontalScrollView01" 
	            android:layout_height="fill_parent" 
	            android:layout_width="fill_parent"
	            android:layout_weight=".70"
	            >
	            <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	                android:layout_width="wrap_content"
	                android:layout_height="fill_parent"
	                android:gravity="center_vertical"
	                android:background="#0000ff"
	                >
	            </RelativeLayout>
	        </com.lid.app.custom_views.SubcategoryScrollView>
	    <ImageView 
	        android:id="@+id/subcategory_right_scroll_img"
	        android:background="@drawable/top_menu_next_category"
	        android:layout_height="wrap_content" 
	        android:layout_width="25dp"
	        android:layout_weight=".15"
	    />
	</LinearLayout>
	


А теперь я отвечу на вопрос который сразу возник — а зачем тут используется мой собственный элемент.

	public class SubcategoryScrollView extends HorizontalScrollView {
		
	        // левая кнопка
		private ImageView left_scroll;
	        // права кнопка
		private ImageView left_scroll;
	        // ширина экрана
		private int display_width;
		
		public SubcategoryScrollView(Context context) {
			super(context);
		}
		
		public SubcategoryScrollView(Context context, AttributeSet attrs) {
	        super(context, attrs);
	    }

	    public SubcategoryScrollView(Context context, AttributeSet attrs, int defStyle) {
	        super(context, attrs, defStyle);
	    }
	    
	    @Override
	    public void onScrollChanged(int l, int t, int oldl, int oldt) {
	    	if(l > 0){
	    		if(this.left_scroll.getVisibility() != View.VISIBLE){
	    			this.left_scroll.setVisibility(View.VISIBLE);
	    		}
	    	}else{
	    		this.left_scroll.setVisibility(View.INVISIBLE);
	    	}

	    	if (this.getWidth() + l >= this.computeHorizontalScrollRange()) {
	        	this.left_scroll.setVisibility(View.INVISIBLE);
	        }else{
	           	this.left_scroll.setVisibility(View.VISIBLE);
	        }
	        super.onScrollChanged(l, t, oldl, oldt);
	    }
	   
	    @Override
	    protected void onDraw (Canvas canvas){
	    	super.onDraw(canvas);
	    	if(this.getScrollX() == 0){
	    		if(this.computeHorizontalScrollRange() <= this.display_width){
	        		this.left_scroll.setVisibility(View.INVISIBLE);
	        	}else{
	        		this.left_scroll.setVisibility(View.VISIBLE);
	        	}
	    	}
	    }
	    
		/**
		 * @param left_scroll the left_scroll to set
		 * @author gron
		 */
		public void setleft_scroll(ImageView left_scroll) {
			this.left_scroll = left_scroll;
		}

		/**
		 * @return the left_scroll
		 * @author gron
		 */
		public ImageView getleft_scroll() {
			return left_scroll;
		}

		/**
		 * @param left_scroll the left_scroll to set
		 * @author gron
		 */
		public void setleft_scroll(ImageView left_scroll) {
			this.left_scroll = left_scroll;
		}

		/**
		 * @return the left_scroll
		 * @author gron
		 */
		public ImageView getleft_scroll() {
			return left_scroll;
		}

		/**
		 * @param display_width the display_width to set
		 */
		public void setDisplayWidth(int display_width) {
			this.display_width = display_width;
		}

		/**
		 * @return the display_width
		 */
		public int getDisplayWidth() {
			return display_width;
		}   
	}
	

Вот зачем мне понадобился данный элемент. При отрисовке и скроле я хочу чтоб рисунки кнопок скрывались.
При отрисовке проверяется ширина экрана ( рассмотрим ниже ) и если ширина всех элементов внутри скрола меньше чем ширина экрана то правая кнопка не отображается.
При скроле тоже кнопки могут скрываться.

Следует учитывать что OnDraw() вызывается после onScrollChanged()
Может читал невнимательно, но этого не нашел и очень долго мучился почему не работает скрол так, как я хочу.

Теперь мы знаем как создавать градиент, преобразовать цвета, создать Drawable из интернет рисунка, скрывать кнопки при скроле и отрисовке. Осталось только соединить все знания в одно и сделать чтоб кнопки добавлялись динамически

И все было очень и очень просто, если б как всегда не нашлись подводные камни.

Создание текстовых полей в горизонтальном скроле динамически


Код буду приводить кусками, извините, копирую из готового проекта.

Для начала сделаем небольшое преобразование для того, чтоб нормально использовать градиент.
	<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	                android:layout_height="fill_parent"
	                android:layout_width="fill_parent"
	                android:id="@+id/main_layout"
	                >
	    <ImageView 
	        android:id="@+id/subcategory_background_img"
	        android:layout_width="fill_parent"
	        android:layout_height="41dp"
	        android:scaleType="fitXY"
	        android:visibility="invisible"
	        android:layout_below="@id/top_menu_layout"
	    />
	    <include layout="@layout/top_menu_subcategories_layout"                        <---------- это наш лейаут с скролом и 2-мя рисунками
	             android:id="@+id/top_menu_categories_layout"
	             android:layout_width="fill_parent"
	             android:layout_height="41dp"
	             android:layout_below="@id/top_menu_layout"
	             />
	</RelativeLayout>
	


Теперь напишем наш код:
	final LinearLayout ll = (LinearLayout) this.findViewById(R.id.top_menu_categories_layout);
	final ImageView background_iv = (ImageView) this.findViewById(R.id.subcategory_background_img);

	// создаем заливку и заливаем ее в наш LinearLeyout
	if (remote_url != null) { 
	        background_iv.setBackgroundDrawable(
	            // наша функция для получение картинки
	            this.LoadImageFromWebOperations(remote_url)
	        );
		background_iv.setVisibility(View.VISIBLE);
		ll.setBackgroundColor(0);
	} else {
		background_iv.setVisibility(View.INVISIBLE);
		// change color in categories menu
		int[] colors = new int[2];
		colors[0] = current_category.getColors().getBackgoundColorAsInt(
	            color1
		);
		colors[1] = current_category.getColors().getBackgoundColorAsInt(
	            color2
		);
		final GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);
		ll.setBackgroundDrawable(gd);
	}


	final SubcategoryScrollView hs_view = (SubcategoryScrollView) this.findViewById(R.id.HorizontalScrollView01);
	final ImageView iv = (ImageView) this.findViewById(R.id.top_menu_select_category);
	final ImageView scroll_left_iv = (ImageView) this.findViewById(R.id.subcategory_left_scroll_img);
	final ImageView scroll_right_iv = (ImageView) this.findViewById(R.id.subcategory_right_scroll_img);
	// внутри HorizontalScroll может быть только один элемент.
	RelativeLayout layout = new RelativeLayout(this);
	TextView tv_old = new TextView(getApplicationContext());

	// теперь по всем подкатегориям
	if (subcategories != null) {
	        // задаем максимальное разрешение
		hs_view.setDisplayWidth(getWindowManager().getDefaultDisplay().getWidth());
		hs_view.scrollTo(0, 0);
		hs_view.setHorizontalScrollBarEnabled(false);
		hs_view.removeAllViews();
		hs_view.setleft_scroll(scroll_left_iv);
		hs_view.setleft_scroll(scroll_right_iv);
		int i = 1;

	        // работать будем с пикселями, но их необходимо преобразовать в связи с разными dpi
		final float left_margin = 5.0f;
		final float top_margin = 2.0f;
		final float padding = 8.0f;
		final float scale = getResources().getDisplayMetrics().density;
		int m_left = (int) (left_margin * scale + 0.5f);
		int t_left = (int) (top_margin * scale + 0.5f);
		int pad = (int) (padding * scale + 0.5f);

                // Subcategory у меня содержит название для текста и несколько других параметров.
	        for (Subcategory item : subcategories) {
			TextView tv = new TextView(this);
			RelativeLayout.LayoutParams lparams = new RelativeLayout.LayoutParams(
				RelativeLayout.LayoutParams.WRAP_CONTENT,
				RelativeLayout.LayoutParams.WRAP_CONTENT
			);
			tv.setText(item.getTitle());
			tv.setTextColor(Color.WHITE);
	                // у первого элемента отступ слева должен отсутствовать
			if (i > 1) {
				lparams.setMargins(m_left, t_left, 0, 0);
			} else {
				lparams.setMargins(0, t_left, 0, 0);
			}
	                // если у нас элемент уже второй то крепим его справа от предыдущего
			if (i > 1) {
				lparams.addRule(RelativeLayout.RIGHT_OF, tv_old.getId());
			}

			tv.setPadding(pad, pad, pad, pad);
			tv.setId(i);
			tv.setLayoutParams(lparams);
	                
	                tv_old = tv;
			layout.addView(tv, lparams);
			i++;
	        }
	        // добавляем наш лейаут в горизонтальный скрол
	        hs_view.addView(layout);
		ll.setVisibility(View.VISIBLE);
	}
	


Все. У нас готов горизонтальный скрол.

Любые замечания приветствуются, программа еще в процессе разработки.

Upd 1. Картинка-градиент из интернета
image

Upd 2. Поправил код, спасибо pyjamec за то, что написал об этом.
Теги:
Хабы:
Всего голосов 54: ↑47 и ↓7+40
Комментарии17

Публикации

Истории

Работа

Ближайшие события

12 – 13 июля
Геймтон DatsDefense
Онлайн
14 июля
Фестиваль Selectel Day Off
Санкт-ПетербургОнлайн
19 сентября
CDI Conf 2024
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн